目录
1、UDP数据报套接字编程
1.1 API介绍
1.1.1 DatagramSocket
DatagramSocket 是UDP Socket,用于 发送 和 接收 UDP数据报。
DatagramSocket 构造方法:
方法 | 方法说明 |
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口,就是port (一般用于服务端) |
DatagramSocket 方法:
方法 | 方法说明 |
void receive(DatagramPacket p) | 从此套接字也就是p,接收数据报 (如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字也就是p,发送数据报 (不会阻塞等待,直接发送) |
void close() |
关闭此数据报套接字 |
这里的方法出现了一个陌生的类 —— DatagramPacket,下面进行介绍这个类。
1.1.2 DatagramPacket
DatagramPacket 是UDP Socket,发送 和 接收 的数据报,表示一个完整的 UDP 数据报。
DatagramPacket 构造方法:
方法 | 方法说明 |
DatagramPacket(byte[] buf,itn length) | 构造一个 DatagramPacket 以用来接收数据报,接收的数据保存在字节数组 (第一个参数buf) 中,接收指定长度 (第二个参数 length) |
DatagramPacket(byte[] buf,int offest,itn length,SocketAddress address) | 构造一个 DatagramPacket 以用来接收数据报,发送的数据为字节数组 (第一个参数buf) 中,从0到指定长度 (第二个参数 length),address 指定目的主机的 IP 和 端口号 |
DatagramPacket 方法:
方法 | 方法说明 |
InetAddress getAddress() | 从接收的数据报中,获取发送端主机 IP 地址;或从发送的数据报中,获取接收端主机 IP 地址。 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接受端主机端口号。 |
byte[] getData() |
获取数据报中的数据 |
构造UDP发送的数据报时候,需要传入 SocketAddress,该对象可以使用 InetSocketAddress 创建
1.1.3 InetSocketAddress
InetSocketAddress (SocketAddress 的子类) 构造方法:
方法 | 方法说明 |
InetSocketAddress(InetAddress addr,int port) | 创建一个 Socket 地址,包含 IP 地址和端口号。 |
1.2 代码示例
1.2.1 UDP Echo Server
/**
* 服务端
*/
public class UdpEchoServer {
// UDP数据报套接字的Socket
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
// 指定了一个固定端口号, 让服务器来使用.
socket = new DatagramSocket(port);
}
// 启动服务器
public void start() throws IOException {
// 启动
System.out.println("服务器启动");
while(true) {
// 循环一次, 就相当于处理一次请求.
// 处理请求的过程, 典型的服务器都是分成三个步骤的.
// 1. 读取请求并解析.
// DatagramPacket 表示一个 UDP 数据报. 此处传入的字节数组, 就保存 UDP 的载荷部分.
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
// 这个 receive 是输出型参数,就是把参数作为 "输出结果",将 请求的数据放到requestPacket中
socket.receive(requestPacket);
// 把读取到的二进制数据, 转成字符串. 只是构造有效的部分. 长度是从 0 到 requestPacket.getLength()的长度
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
// 2. 根据请求, 计算响应. (服务器最关键的逻辑)
// 但是此处写的是回显服务器. 这个环节相当于省略了.
String response = process(request);
// 3. 把响应返回给客户端
// 这里不能是 response.length 因为这个是 字符的个数
// 而这里需要的是 字节的个数
// 此处还需要 UDP 协议自身没有保存对方的信息,也就是 目的IP 和 目的端口号
// 使用requestPacket.getSocketAddress() 进行获取
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length,requestPacket.getSocketAddress());
socket.send(responsePacket);
// 打印一个日志
System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
requestPacket.getPort(),request, response);
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
udpEchoServer.start();
}
}
这里需要理解 receive方法里面的 理解输出型参数。
1.2.2 UDP Echo Client
/**
* 客户端
*/
public class UdpEchoClient {
// UDP数据报套接字的Socket
private DatagramSocket socket = null;
// UDP 本身不保存对端的信息, 就自己的代码中保存一下
private String serverIp; // 目的IP
private int serverPort; // 目的端口号
// 和服务器不同, 此处的构造方法是要指定访问的服务器的地址.
public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
socket = new DatagramSocket(); // 这里是源IP 和 源端口
}
// 开始客户端
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while(true) {
// 1. 从控制台读取用户输入的内容.
System.out.println("请输入要发送的内容:");
if (!scanner.hasNext()) {
break;
}
String request = scanner.next();
// 2. 把请求发送给服务器, 需要构造 DatagramPacket 对象.
// 构造过程中, 不光要构造载荷, 还要设置服务器的 IP 和端口号
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
// 3. 发送数据报
socket.send(requestPacket);
// 4. 接收服务器的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
// 5. 从服务器读取的数据进行解析, 打印出来.
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);
udpEchoClient.start();
}
}
这里客户端的的 目的IP 就是本机的 IP 地址。
2、TCP流套接字编程
和刚才的UDP是比较相似的。TCP的核心特点,面向字节流,读写数据的基本单位就是字节byte
2.1 API介绍
2.1.1 ServerSocket
ServerSocket 是构造TCP服务端 Socket的API。这个是专门给服务器用的。
ServerSocket 构造方法:
方法 | 方法说明 |
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口。 |
ServerSocket 方法:
方法 | 方法说明 |
Socket accept() | 开始监听制定的端口(这个端口就是创建时绑定的),游客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的链接,否则阻塞等待。 |
void close | 关闭此套接字 |
因为TCP 是有连接的,这个在上一篇博客中介绍到了。所以这里是使用 accept 这个方法来联通链接的关键操作。
2.1.2 Socket
Socket 是客户端 Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端 Socket,都是装房建立连接后,保存对端的信息,及用来与对方收发数据的。
服务端和客户端都会用。
Socket 构造方法:
方法 | 方法说明 |
Socket(String host,int port) | 创建一个客户端流套接字Socket,并与对应IP 的主机上对应端口的进程建立连接。 |
这里的端口号 和 IP 也就可以理解为 服务器的IP 和 服务器的端口
Socket 方法:
方法 | 方法说明 |
InetAddress getInetAddress() | 返回套接字所链接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回次套接字的输出流 |
TCP这里没有send和receive操作,这里是通过IO那里介绍的字节流的输入和输出来完成的。
2.2 代码示例
2.2.1 TCP Echo Server
/**
* 服务端
*/
public class TcpEchoServer {
private ServerSocket serverSocket = null;
// 这里和 UDP 服务器类似, 也是在构造对象的时候, 绑定端口号.
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("启动服务器");
// 这种情况一般不会使用 fixedThreadPool, 意味着同时处理的客户端连接数目就固定了.
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
// tcp 来说, 需要先处理客户端发来的连接.
// 通过读写 clientSocket, 和客户端进行通信.
// 如果没有客户端发起连接, 此时 accept 就会阻塞.
// 主线程负责进行 accept, 每次 accept 到一个客户端, 就创建一个线程, 由新线程负责处理客户端的请求.
Socket clientSocket = serverSocket.accept();
// 这个是单个线程进行处理
// processConnection(clientSocket);
// 使用多线程的方式来调整
// Thread t = new Thread(() -> {
// processConnection(clientSocket);
// });
// t.start();
// 使用线程池来调整
executorService.submit(() -> {
processConnection(clientSocket);
});
}
}
// 处理一个客户端的连接.
// 可能要涉及到多个客户端的请求和响应.
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
// 针对 InputStream 套了一层
Scanner scanner = new Scanner(inputStream);
// 针对 OutputStream 套了一层
PrintWriter writer = new PrintWriter(outputStream);
// 分成三个步骤
while (true) {
// 1. 读取请求并解析. 可以直接 read, 也可以借助 Scanner 来辅助完成.
if (!scanner.hasNext()) {
// 连接断开了
System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
break;
}
String request = scanner.next();
// 2. 根据请求计算响应
String response = process(request);
// 3. 返回响应到客户端
// outputStream.write(response.getBytes());
writer.println(response);
writer.flush();
// 打印日志
System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
2.2.2 TCP Echo Client
/**
* 客户端
*/
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIp, int serverPort) throws IOException {
// 直接把字符串的 IP 地址, 设置进来.
// 127.0.0.1 这种字符串
socket = new Socket(serverIp, serverPort);
}
public void start() {
Scanner scanner = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 为了使用方便, 套壳操作
Scanner scannerNet = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
// 从控制台读取请求, 发送给服务器.
while (true) {
// 1. 从控制台读取用户输入
String request = scanner.next();
// 2. 发送给服务器
writer.println(request);
// 加上刷新缓冲区操作, 才是真正发送数据
writer.flush();
// 3. 读取服务器返回的响应.
String response = scannerNet.next();
// 4. 打印到控制台
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
client.start();
}
}
到这里呢对于这个 UDP数据报套接字编程 和 TCP流套接字编程的简单的代码就演示完成了,并且介绍了对应的一些方法来进行编程代码,到这里Socket阶段就结束了。
本次的分享就到这里了。
感觉文章不错的话,期待你的一键三连哦,你的鼓励就是我的动力,让我们一起加油,顶峰相见。拜拜喽~~我们下次再见💓💓💓💓💓💓💓💓💓💓💓💓