粘包和拆包

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();
        }
    }
}

运行结果:

客户端只发送了一条数据,服务端却分了多条接收


服务器


客户端

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值