Java--网络编程基础
网络编程
网络编程指的是编写跨多个设备(计算机)执行的程序,设备使用网络彼此连接
网络编程中主要解决的问题
- 如何准确定位网络上一台或多台主机--IP地址
- 如何定位主机上的特定的应用进程--端口号
- 如何进行可靠高效的数据传输
J2SE API的java.net包中包含一组类和接口,它们提供低级别的通信详细信息,开发者可编写专注于解决手头问题的程序。java.net包提供对两种常见网络协议的支持
-
TCP - TCP代表传输控制协议,它允许两个应用程序之间的可靠通信。TCP通常用于Internet协议,称为TCP/IP
-
UDP
Socket 编程
简介
Socket(套接字)是使用TCP提供通信机制在两台计算机之间进行通信。客户端程序在通信的末尾创建一个套接字,并尝试将套接字连接到服务器。建立连接后,服务器会在通信结束时创建一个套接字对象。客户端和服务器现在可以通过写入和读取套接字进行通信
java.net.Socket类表示一个套接字,java.net.ServerSocket类为服务器程序提供了一种监听客户端并与它们建立连接的机制。使用Socket(套接字)在两台计算机之间建立TCP连接时,会发生以下步骤
-
服务器实例化ServerSocket对象,表示要在哪个端口号上进行通信
-
服务器调用ServerSocket类的accept()方法。此方法等待,直到客户端连接到给定端口上的服务器
-
在服务器等待之后,客户端实例化Socket对象,指定要连接的服务器名称(IP地址)和端口号
-
Socket类的构造函数尝试将客户端连接到指定的服务器和端口号。如果建立了通信,则客户端现在具有能够与服务器通信的Socket对象
-
在服务器端,accept()方法返回对连接到客户端套接字的服务器上的新套接字的引用
建立连接后,可以使用I/O流进行通信。每个套接字都有一个OutputStream和一个InputStream。客户端的OutputStream连接到服务器的InputStream,客户端的InputStream连接到服务器的OutputStream
TCP是双端通信协议,因此可以同时跨两个流发送数据
Socket通信入门案例
搭建简单的Socket通信入门案例,通过一次性的客户端发送和服务端接收数据,掌握并了解
-
Socket服务端和客户端的基本编程
-
传输编码统一指定,防止乱码
服务端监听一个端口,等待连接的到来
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* @describe 服务端监听一个端口,等待连接的到来
*/
public class SocketServer {
public static void main(String[] args) throws IOException {
//服务端监听指定端口
ServerSocket serverSocket = new ServerSocket(8888);
//服务端阻塞等待连接
System.out.println("服务端阻塞等待连接端口8888……");
Socket socket = serverSocket.accept();
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder builder = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
builder.append(new String(bytes,0,len, StandardCharsets.UTF_8));
}
System.out.println("服务端接收到客户端的发来的信息:"+builder.toString());
//依次关闭资源
inputStream.close();
socket.close();
serverSocket.close();
}
}
客户端指定ip(域名亦可)和端口,连接到指定的服务端,进行数据传输
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* @describe 客户端指定ip和端口,连接到指定的服务端,进行数据传输
*/
public class SocketClient {
public static void main(String[] args) throws IOException {
//客户端指定ip(域名亦可)和端口,与服务端建立连接
Socket socket = new Socket("127.0.0.1", 8888);
//获取输出流
OutputStream outputStream = socket.getOutputStream();
//输出数据
outputStream.write("hello server".getBytes(StandardCharsets.UTF_8));
//依次关闭资源
outputStream.close();
socket.close();
}
}
测试时需要先启动服务端再启动客户端
入门案例优化
双向通信
服务端添加返回响应数据的操作
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* @describe 服务端监听一个端口,等待连接的到来
*/
public class SocketServer {
public static void main(String[] args) throws IOException {
//服务端监听指定端口
ServerSocket serverSocket = new ServerSocket(8888);
//服务端阻塞等待连接
System.out.println("服务端阻塞等待连接端口8888……");
Socket socket = serverSocket.accept();
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder builder = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
builder.append(new String(bytes,0,len, StandardCharsets.UTF_8));
}
System.out.println("server get message from client: "+builder.toString());
//服务端返回响应的数据
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello client,i get the message".getBytes(StandardCharsets.UTF_8));
//依次关闭资源
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
}
}
客户端添加接收服务端返回数据的操作
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* @describe 客户端指定ip和端口,连接到指定的服务端,进行数据传输
*/
public class SocketClient {
public static void main(String[] args) throws IOException {
//客户端指定ip(域名亦可)和端口,与服务端建立连接
Socket socket = new Socket("127.0.0.1", 8888);
//获取输出流
OutputStream outputStream = socket.getOutputStream();
//输出数据
outputStream.write("hello server".getBytes(StandardCharsets.UTF_8));
//通过shutdownOutput告诉服务端,客户端已发送完数据,后续只能接受数据
socket.shutdownOutput();
//接收服务端返回的数据
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder builder = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
builder.append(new String(bytes,0,len,StandardCharsets.UTF_8));
}
System.out.println("client get message from server: " + builder.toString());
//依次关闭资源
inputStream.close();
outputStream.close();
socket.close();
}
}
服务端并发处理
在上面的例子中,服务端仅仅只是接受了一个Socket请求,并处理了它,然后就结束了,但是在实际开发中,一个Socket服务往往需要服务大量的Socket请求
循环接受请求并处理 -- 不建议
缺点:循环处理多个Socket请求,不过当一个请求的处理比较耗时的时候,后面的请求将被阻塞
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* @describe 服务端监听一个端口,等待连接的到来
*/
public class SocketServer {
public static void main(String[] args) throws IOException {
//服务端监听指定端口
ServerSocket serverSocket = new ServerSocket(8888);
//服务端阻塞等待连接
System.out.println("服务端阻塞等待连接端口8888……");
while (true) {
Socket socket = serverSocket.accept();
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder builder = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
builder.append(new String(bytes,0,len, StandardCharsets.UTF_8));
}
System.out.println("server get message from client: "+builder.toString());
//服务端返回响应的数据
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello client,i get the message".getBytes(StandardCharsets.UTF_8));
//依次关闭资源
outputStream.close();
inputStream.close();
socket.close();
}
}
}
线程池接收请求并处理 -- 推荐
用多线程的方式来处理Socket,即每有一个Socket请求的时候,就创建一个线程来处理它。不过在实际生产中,创建的线程会交给线程池来处理,为了
-
线程复用,创建线程耗时,回收线程慢
-
防止短时间内高并发,指定线程池大小,超过数量将等待,方式短时间创建大量线程导致资源耗尽,服务挂掉
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @describe 服务端监听一个端口,等待连接的到来
*/
public class SocketServer {
public static void main(String[] args) throws IOException {
//服务端监听指定端口
ServerSocket serverSocket = new ServerSocket(8888);
//设置一个读取的超时时间,当超过指定的时间后,还没有读到数据,就假定这个连接无用
//然后抛异常,捕获异常后关闭连接就可以了
serverSocket.setSoTimeout(10000);
//服务端阻塞等待连接
System.out.println("服务端阻塞等待连接端口8888……");
//如果使用多线程,那就需要线程池,防止并发过高时创建过多线程耗尽资源
ExecutorService threadPool = Executors.newFixedThreadPool(100);
while (true) {
Socket socket = serverSocket.accept();
Runnable runnable = () -> {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder builder = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
builder.append(new String(bytes, 0, len, StandardCharsets.UTF_8));
}
System.out.println(String.format("server get message from client: %s,服务处理线程: %s",builder.toString(),Thread.currentThread().getName()));
//服务端返回响应的数据
outputStream = socket.getOutputStream();
outputStream.write("hello client,i get the message".getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
} finally {
//依次关闭资源
release(inputStream,outputStream,socket);
}
};
threadPool.execute(runnable);
}
}
/**
* 释放资源
* @param inputStream
* @param outputStream
* @param socket
*/
public static void release(InputStream inputStream,OutputStream outputStream,Socket socket){
try {
if (inputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
URL网络编程
URL(Uniform Resourse Locator):统一资源定位符,表示Internet上某一个资源的地址
- 是一种具体的URI,即URL可以用来表示一个资源,而且指明了如何定位这个资源
- 通过URL可以访问internet上各种网络资源,比如www,ftp站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或者其他资源
URL基本结构
- <传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
- 例如:http://192.168.1.20:8080/wen/index.html#a?username=wen&password=123456
- #片段名即锚点,例如电子书的章节
- 参数列表格式:参数名=参数值&参数名=参数值……
URL应用实例:从网络中下载某个资源保存在本地
import org.junit.Test;
import java.io.*;
import java.net.*;
import java.util.Objects;
public class DownloadTest {
@Test
public void download(){
InputStream inputStream = null;
FileOutputStream outputStream = null;
HttpURLConnection connection = null;
try {
URL url = new URL("https://www.keaidian.com/uploads/allimg/190424/24110307_0.jpg");
connection = (HttpURLConnection) url.openConnection();
inputStream = connection.getInputStream();
outputStream = new FileOutputStream("24110307_1.jpg");
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
outputStream.write(buffer,0,len);
}
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (Objects.nonNull(outputStream)) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (Objects.nonNull(inputStream)) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (Objects.nonNull(connection)) {
connection.disconnect();
}
}
}
}