- 获取pdf:密码7281
- 专栏目录首页:【专栏必读】考研湖科大教书匠计算机网络笔记导航
此部分为补充内容,主要使用Java实现TCP和UDP通信
一:UDP通信
(1)Java数据报套接字通信模型
Java UDP通信模型:Java中使用UDP协议通信,主要依靠一下两个类
DatagramSocket
:创建UDP SocketDatagramPacket
:是UDP Socket发送和接受的数据报
一次发送和接收UDP数据报流程如下
多个请求流程如下
(2)API接口说明
A:DatagramSocket
DatagramSocket:用于构造UDP Socket,以发送和接收UDP数据报
构造方法如下
方法签名 | 方法说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报 Socket,绑定到本机任意一个随机端口 ,一般用于客户端 |
DatagramSocket(int port) | 创建一个UDP数据报 Socket,绑定到本机任意指定端口 ,一般用于服务端 |
成员方法如下
方法签名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从该Socket中接收数据报(如果未收到则会阻塞等待) |
void send(DatagramPacket p) | 由该Socket发送数据报(不会阻塞等待,直接发送) |
void close() | 关闭此Socket |
B:DatagramPacket
DatagramPacket:是UDP Socket发送和接收的数据报
构造方法如下
方法签名 | 方法说明 |
---|---|
DatagramPacket(byet[] buf, int length) | 用于接收数据报,接收的数据保存在buf 数组中,length 指定接收长度 |
DatagramPacket(byet[] buf, int offset, int length, SocketAddress address) | 用于发送数据报,发送的数据为buf 数组,范围为从0到length ,address 是目的主机的IP和端口号 |
成员方法如下
方法签名 | 方法说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中获取发送端主机IP地址;或从发送的数据报中获取接收端主机IP地址 |
int getPort() | 从接收的数据报中获取发送端主机端口号;或从发送的数据报中获取接收端主机端口号 |
bye[] getData() | 获取数据报中的数据 |
(3)示例
- 注意:这个功能比较简单,但主要目的是为了演示上面所讲API的用法
A:代码
服务端IP
地址设置为127.0.0.1
,也即本地环回,也即自己发自己收,数据会完整走一遍协议
服务端:UDPServer
:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPServer {
// 构造DatagramSocket
private DatagramSocket socket = null;
public UDPServer(int port) throws SocketException {
// 服务端需要绑定一个端口号
socket = new DatagramSocket(port);
}
// 服务端启动
public void start() throws IOException {
System.out.println("服务器启动!");
// 服务端随时等待接收客户端请求
while (true) {
/*
1. 接受请求并解析
构造一个DatagramPacket对象requestPacket用于接受客户端发来的数据报,保存在byte[]
为了方便查看信息,需要使用 requestPacket的getData()方法将其转换为String
*/
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
/*
2. 拿到客户端的请求request后,将其交给一个方法process进行处理,process方法会返回响应response
*/
String response = process(request);
/*
3. 将响应回复给客户端
构造一个DatagramPacket对象responsePacket用于给客户端回复响应response ,response是String
所以需要使用用getBytes()方法将其转化为byte[]
客户端发来的requestPacket数据报中携带有客户端的Ip地址和端口号,使用getSocketAddress()获得
*/
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
// 打印信息
System.out.println("【客户端IP: " + requestPacket.getAddress().toString()
+ "客户端口号:" + requestPacket.getPort() + "】"
+ ":\"" + request + "\"" + ", 服务端回复: " + "\"" + response + "\"");
}
}
// 回显服务器,客户端发什么服务端就回复什么
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
// 服务端监听9090端口
UDPServer server = new UDPServer(9090);
server.start();
}
}
客户端:UDPClient
:
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UDPClient {
DatagramSocket socket = null;
// 客户端需要指定好服务端的IP和端口号,然后构建一个Socket地址
private SocketAddress ADDRESS = null;
public UDPClient(String serverIP, int serverPort) throws SocketException {
ADDRESS = new InetSocketAddress(serverIP, serverPort);
socket = new DatagramSocket();
}
// 客户端启动
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true) {
// 1. 读取客户端用户输入
System.out.print("input: ");
String request = scanner.next();
/*
2. 发送请求给服务端
构造一个DatagramPacket对象requestPacket用于给服务端发送数据报,注意需要将String转换为byte[]
同时传入服务端的ADDRESS(ip地址和port)
*/
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
ADDRESS);
socket.send(requestPacket);
// 3. 从服务端获取响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
// 打印信息
System.out.println("服务端回复:" + response);
}
}
public static void main(String[] args) throws IOException {
UDPClient client = new UDPClient("127.0.0.1", 9090);
client.start();
}
}
B:效果展示
一个服务端一个客户端
一个服务端多个客户端
注意如果需要运行多个UDPClient
实例,需要在Run/Debug Configurations
中选中Allow multiple instances
这里启动三个客户端
C:分析
对于服务端(UDPServer
类):
-
构造方法(
public UDPServer(int port)
)- 需要建立一个
DatagramSocket
并绑定一个指定的端口号port
,也即DatagramSocket socket =new DatagramSocket(port)
。服务端绑定端口号的原因是因为程序运行在服务器上是确定的、可控的
- 需要建立一个
-
服务端处理逻辑(
public void start()
)-
①:接受请求并解析
- 需要构造一个
DatagramPacket
用于接受客户端发来的数据报,会保存在byte[]
中,也即DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096)
- 接受行为由
socket
完成,也即socket.receive(requestPacket)
- 接受到的
requestPacket
其类型为byte[]
,所以需要把它转为String
以便于查看,也即String request = new String(requestPacket.getData(), 0, requestPacket.getLength())
- 需要构造一个
-
②:处理请求
- 拿到解析后的
request
后,需要对该request
进行处理(交给方法process
),不同的业务逻辑会有不同的处理方法。这里我们只是简单的“回显”一下即可,也即客户端发什么服务端就回复什么
- 拿到解析后的
-
③:将处理结果(响应)回复给客户端
- 需要构造一个
DatagramPacket
用于给客户端回复响应response
,在构造时要将类型为String
的respnse
转化为byte[]
,同时使用requestPacket.getSocketAddress()
获取到客户端的IP
和port
(因为服务端总要知道客户端的具体地址才能发送),也即DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress())
- 发送行为由
socket
完成,也即socket.send(responsePacket);
- 需要构造一个
-
④:打印相关信息
-
-
main
方法- 构造
UDPServer
对象,并绑定指定端口号,如9090
,也即UDPServer server = new UDPServer(9090)
- 启动服务端,也即
server.start()
- 构造
对于客户端(UDPClient
类):
-
构造方法(
public UDPClient(String serverIP, int serverPort)
)-
需要建立一个
DatagramSocket
,但不指定端口号,也即DatagramSocket socket =new DatagramSocket()
。客户端无需绑定端口号是因为客户端上运行状况复杂,端口号占用情况各不相同,无法保证所指定的端口号在数据报来临时一定是空闲的,所以这里让系统自动指定空闲端口号即可 -
需要建立一个
InetSocketAddress
,传入服务端serverIP
和serverPort
,也即SocketAddress ADDRESS = new InetSocketAddress(serverIP, serverPort)
,这里的serverPort
就是服务端中所指定的那个port
。之所以这样做是因为客户端发送时必须要知道服务端的IP
地址和port
InetSocketAddress
是SocketAddress
的子类
-
-
客户端处理逻辑(
public void start()
)-
①:读取客户端中用户的输入
- 使用
Scanner
接受即可,也即String request = scanner.next()
- 使用
-
②:发送请求给服务端
- 需要构造一个
DatagramPacket
给服务端发送数据报,注意需要将类型String
的request
转化为byte[]
,同时传入服务端的ADDRESS
。也即DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, ADDRESS);
- 发送行为由
socket
完成,也即socket.send(requestPacket)
- 需要构造一个
-
③:从服务端获取响应
- 需要构造一个
DatagramPacket
用于接受服务端端发来的数据报,会保存在byte[]
中,也即DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096)
- 接受行为由
socket
完成,也即socket.receive(responsePacket)
- 接受到的
responsePacket
其类型为byte[]
,所以需要把它转为String
以便于查看,也即String response = new String(responsePacket.getData(), 0, resoibsePacket.getLength())
- 需要构造一个
-
④:打印相关信息
-
-
main
方法- 构造
UDPClient
对象,并给定服务端IP
和port
,也即UDPClient client = new UDPClient("127.0.0.1", 9090)
- 启动客户端,也即
client.start()
- 构造
二:TCP通信
(1)Java流套接字通信模型
Java TCP通信模型:Java中使用TCP协议进行通信,主要依靠以下两个类
ServerSocket
:是创建TCP服务端Socket的APISocket API
:是客户端Socket,或服务端中接收到客户端连接(accept方法)的请求后,返回服务端Socket
通信流程如下
(2)API接口说明
A:ServerSocket
ServerSocket:用于创建TCP服务端流套接字Socket
构造方法如下
方法签名 | 方法说明 |
---|---|
ServerSocet(int port) | 创建一个服务端流套接字 Socket ,并绑定到指定端口 |
成员方法如下
方法签名 | 方法说明 |
---|---|
Socket accept() | 开始监听端口,当有客户端连接后会返回一个服务端Socket 对象,并基于该Socket 与客户端建立连接,否则阻塞等待 |
void close() | 关闭此套接字 |
B:Socket
Socket :是客户端的Socket
(当然也会给服务端用,上面表格说过,当有客户端连接服务端后,会返回一个服务端Socket
)
构造方法如下
方法签名 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket ,并和对应IP 的主机上、对应端口的进程建立连接 |
成员方法如下
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
(3)示例
- 注意:这个功能比较简单,但主要目的是为了演示上面所讲API的用法
A:代码
服务端IP
地址设置为127.0.0.1
,也即本地环回,也即自己发自己收,数据会完整走一遍协议
服务端:TCPServer:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TCPServer {
// 创建监听套接字
private ServerSocket listenSocket = null;
public TCPServer(int port) throws IOException {
// 监听套接字绑定指定端口
listenSocket = new ServerSocket(port);
}
// 服务器启动
public void start() throws IOException {
System.out.println("服务器启动!");
while (true) {
// 调用监听套接字的accept()连接客户端,并返回Socket类型的clientSocket
// 将clientSocket传递给具体处理连接的方法processConnection()进行处理
Socket clientSocket = listenSocket.accept();
// 进行处理
processConnection(clientSocket);
}
}
// 用于处理连接
private void processConnection(Socket clientSocket) throws IOException {
System.out.println("【客户端IP: " + clientSocket.getInetAddress().toString()
+ "客户端口号:" + clientSocket.getPort() + "】"
+ "已上线");
// 处理请求
// 打开inputStream和outputStream
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
while (true) {
// 1. 读取请求并解析
Scanner scanner = new Scanner(inputStream);
if (!scanner.hasNext()) {
// 如果读完了那么连接可以断开了
System.out.println("【客户端IP: " + clientSocket.getInetAddress().toString()
+ "客户端口号:" + clientSocket.getPort() + "】"
+ "下线");
break;
}
String request = scanner.next();
// 2. 根据请求计算响应,具体处理函数为process
String response = process(request);
// 3. 响应回复给客户端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
// 打印信息
System.out.println("【客户端IP: " + clientSocket.getInetAddress().toString()
+ "客户端口号:" + clientSocket.getPort() + "】"
+ ":\"" + request + "\"" + ", 服务端回复: " + "\"" + response + "\"");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭套接字
// listenSocket在TCP服务端程序中只有一个,所以不太可能把文件描述符占满
// 而clientSocket 每遇到一个客户端都要创建一个,所以一定要注意关闭
clientSocket.close();
}
}
// 业务逻辑函数
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TCPServer server = new TCPServer(9090);
server.start();
}
}
客户端:TPCClient:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
// 建立Socket对象
private Socket socket = null;
public TCPClient(String serverIP, int serverPort) throws IOException {
// 指定服务端IP和端口号
socket = new Socket(serverIP, serverPort);
}
// 客户端启动
public void start () throws IOException {
Scanner scanner = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
while (true) {
// 1. 获取用户输入
System.out.print("input: ");
String request = scanner.next();
// 2. 发送请求给服务端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();
// 3. 从服务端获得响应
Scanner responseScanner = new Scanner(inputStream);
String response = responseScanner.next();
// 打印信息
System.out.println("服务端回复:" + response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TCPClient client = new TCPClient("127.0.0.1", 9090);
client.start();
}
}
B:效果展示
C:分析
对于服务端(TCPServer
类):
-
构造方法(
public TCPServer(int port)
)- 需要建立一个
ServerSocket
类型的监听套接字,用于监听客户端的请求连接,也即private ServerSocket listenSocket = new ServerSocket(port)
- 需要建立一个
-
服务端处理逻辑(
public void start()
)- 不断循环一直监听客户端的连接,当有客户端连接之后监听套接字会返回
Socket
类型的套接字用于处理这个连接,也即Socket clientSocket = listenSocket.accept()
- 具体处理连接的过程交由
processConnection()
方法进行,也即processConnection(ClientSocket)
- 不断循环一直监听客户端的连接,当有客户端连接之后监听套接字会返回
-
服务端处理连接(
private void processConnection(Socket clientSocket)
)-
①:打开套接字的输入流和输出流
clientSocket
里的请求内容保存在其InputStream
中,最终服务端回复响应时要将该响应写入到其OutputStream
中,也即InputStream inputStream = clientSocket.getInputStream()
和OutputStream outputStream = clientSocket.getOutputStream()
-
②:读取
InputStream
中的请求并解析- 使用
Scanner
进行读取比较方便,也即Scanner scanner = new Scanner(inputStream)
- 读取时注意随时判断是否读取完毕,如果读取完毕表示客户端可以下线了
- 读取好的请求保存在
request
中,也即String request = scanner.next()
- 使用
-
③:根据请求得到响应
- 拿
request
后,需要对该request
进行处理(交给方法process
),不同的业务逻辑会有不同的处理方法。这里我们只是简单的“回显”一下即可,也即客户端发什么服务端就回复什么
- 拿
-
④:将响应写入到
OutputStream
中- 使用
PrintWriter
写入比较方便,也即PrintWriter printWriter = new PrintWriter(outputStream)
、printWriter.println(response)
- 写入完成之后必要忘记刷新一下,也即
printWriter.flush()
- 使用
-
⑤:打印相关信息
-
⑥:关闭
clientSocket
套接字listenSocket
在TCP服务端程序中只有一个,所以不太可能把文件描述符占满,而clientSocket
每遇到一个客户端都要创建一个,所以一定要注意关闭,也即clientSocket.close()
-
-
main
方法- 构造
TCPServer
对象,并绑定指定端口号,如9090
,也即TCPServer server = new TCPServer(9090)
- 启动服务端,也即
server.start()
- 构造
对于服务端(TCPClient
类):
-
构造方法(
public TCPClient(String serverIP, int serverPort)
)- 需要建立一个
Socket
类型的套接字,并传入服务端IP
和Port
,也即private Socket socket = new Socket(serverIP, serverPort)
- 需要建立一个
-
客户端处理逻辑(
public void start ()
)-
①:打开套接字的输入流和输出流
- 客户端会把它的请求写入到
InputStream
中,服务端回复响应后客户端会从OutputStream
中读取,也即InputStream inputStream = socket.getInputStream()
、OutputStream outputStream = socket.getOutputStream()
- 客户端会把它的请求写入到
-
②:读取客户端用户输入并构造请求
- 使用
Scanner
接受即可,也即String request = scanner.next()
- 使用
-
③:将请求写入到
OutputStream
中- 使用
PrintWriter
写入比较方便,也即PrintWriter printWriter = new PrintWriter(outputStream)
、printWriter.println(request)
- 写入完成之后必要忘记刷新一下,也即
printWriter.flush()
- 使用
-
④:读取
InputStream
中的响应- 使用
Scanner
进行读取比较方便,也即Scanner responseScanner = new Scanner(inputStream)
、String response = responseScanner.next()
- 使用
-
⑤:打印相关信息
-
-
main
方法- 构造
TCPClient
对象,并给定服务端IP
和Port
,也即TCPClient client = new TCPClient("127.0.0.1", 9090)
- 启动客户端,也即
client.start()
- 构造