TCP的粘包与拆包
什么是粘包?
粘包(Packet Sticking): 粘包是指发送方在传输数据时,将多个小的数据包粘在一起发送,而接收方在接收时可能无法正确地区分这些数据包,从而形成了一个“粘在一起”的包。这通常发生在连续的send操作中,多个数据包可能会被合并成一个大的数据包一起发送
举个例子
假设有一个发送方(Sender)和一个接收方(Receiver),它们之间通过TCP进行通信。现在,Sender想要发送三个小的数据块,分别是"Message1"、“Message2"和"Message3”。
1、未发生粘包:
Sender发送 “Message1”,TCP将其封装成一个TCP段发送到网络。
Receiver接收到TCP段,正确解析出 “Message1”。
2、发生粘包:
Sender发送 “Message2”,但TCP并没有立即发送,而是将 “Message1” 和 “Message2” 合并成一个较大的TCP段发送到网络。
Receiver接收到TCP段,此时可能需要进行额外的处理来正确分离出 “Message1” 和 “Message2”。
3、更严重的粘包:
Sender发送 “Message3”,但TCP并没有立即发送,而是将之前的 “Message1”、“Message2” 和 “Message3” 合并成一个更大的TCP段发送到网络。
Receiver接收到TCP段,需要更复杂的处理来正确分离出三个消息。
代码展示
客户端不断给服务端发送端数据
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket clientSocket = new Socket("localhost", 12345);
OutputStream outputStream = clientSocket.getOutputStream();
// 模拟发送多个小数据包
for (;;) {
byte[] data = "1".getBytes();
outputStream.write(data);
outputStream.flush();
System.out.println("Sent data to server: " + "1");
// 模拟发送间隔
Thread.sleep(1);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
服务器
package com.hwq.test.socket;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("Server waiting for connections...");
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getInetAddress());
// 处理客户端连接的线程
new Thread(() -> {
try {
InputStream inputStream = clientSocket.getInputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
String receivedData = new String(buffer, 0, bytesRead);
System.out.println("Received data from client: " + receivedData);
}
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
很明显发生了粘包问题,我们客户端每次发送的数据都是1,而服务端却接收多个1
什么是拆包?
拆包(Packet Splitting):
拆包是指发送方在传输数据时,将一个大的数据包拆分成多个小的数据包发送,而接收方在接收时可能无法正确地组合这些小的数据包,从而导致数据包的拆分。
与粘包相反,拆包一般是在用户发送的数据包过大产生的问题
举个栗子
想象一下你在使用聊天应用程序发送消息。TCP拆包问题可能会发生在接收方,导致接收到的消息不是按照你发送的方式完整显示。
假设你发送了两条消息:
1、“Hello, how are you?”
2、“I’m fine, thank you!”
这两条消息被发送到服务端并通过TCP传输到接收方。由于TCP是一个流协议,它可能会在接收方那里将这两个消息组合在一起,形成一个连续的数据流。接收方在读取数据时可能会遇到问题,因为它不知道消息的边界在哪里。
接收方可能会收到类似以下的数据:
"Hello, how are you?I'm fine, thank you!"
接收方可能会困惑,不知道如何正确地将这个数据流分割成两条消息。这就是一个TCP拆包的例子。
代码示例
客户端
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket clientSocket = new Socket("localhost", 12345);
OutputStream outputStream = clientSocket.getOutputStream();
// 模拟发送一个大数据块,服务端可能将其拆分
String largeMessage = "This is a large message that may be split by the server into smaller chunks.";
for (int i = 0; i < 10; i++) {
largeMessage += largeMessage;
}
byte[] data = largeMessage.getBytes();
outputStream.write(data);
outputStream.flush();
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("Server waiting for connections...");
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getInetAddress());
// 处理客户端连接的线程
new Thread(() -> {
try {
InputStream inputStream = clientSocket.getInputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
String receivedData = new String(buffer, 0, bytesRead);
System.out.println("Received data from client: " + receivedData);
}
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
客户端只发送了一条数据,服务端却分了多条接收