0
点赞
收藏
分享

微信扫一扫

网络基础知识之TCP粘包、拆包

Yaphets_巍 04-01 12:00 阅读 15

在互联网通信中,TCP是一种可靠的传输协议。它将数据分成多个小的数据包进行传输,并在接收端重新组装这些数据包,以确保数据的完整性和正确性。然而,由于网络传输的复杂性,TCP在传输过程中可能会出现粘包(Packet Sticking)和拆包(Packet Splitting)的

TCP粘包的原因和表现

TCP粘包指的是发送方在发送数据时,将多个逻辑上独立的数据包粘合在一起发送,导致接收方在接收时无法正确地区分这些数据包。造成TCP粘包的原因有多种,包括网络传输的延迟、缓冲区的限制、发送方的发送策略等。

TCP粘包的表现形式有两种:多个数据包粘合在一起,形成一个大的数据包。

  1. 多个数据包合并成一个数据包,但是在接收端无法正确地解析出每个数据包。
  2. 多个数据包合并成一个数据包,但是在接收端无法正确地解析出每个数据包。

TCP拆包的原因和表现

TCP拆包指的是发送方在发送数据时,将一个逻辑上独立的数据包拆分成多个小的数据包发送,导致接收方在接收时无法正确地组装这些数据包。TCP拆包的原因主要是由于发送方发送数据的速度过快,接收方处理数据的速度没有跟上

TCP拆包的表现形式有两种:

  1. 一个数据包被拆分成多个小的数据包,接收方无法正确地组装这些数据包。
  2. 一个数据包被拆分成多个小的数据包,但是在接收端可以正确地解析出每个数据包

代码实现

#include <Tcpwork.h>

int main() {
	TcpMessager parser;

	// 模拟接收到的数据
	// 假设有两个消息:
	// 消息1: 长度=5, 内容="Hello"
	// 消息2: 长度=6, 内容="World!"
	uint32_t len1 = 5;
	uint32_t len2 = 6;
	std::string msg1 = "Hello";
	std::string msg2 = "World!";
	std::vector<char> data;

	// 构造消息1
	data.insert(data.end(), reinterpret_cast<char*>(&len1), reinterpret_cast<char*>(&len1) + sizeof(uint32_t));
	data.insert(data.end(), msg1.begin(), msg1.end());

	// 构造消息2
	data.insert(data.end(), reinterpret_cast<char*>(&len2), reinterpret_cast<char*>(&len2) + sizeof(uint32_t));
	data.insert(data.end(), msg2.begin(), msg2.end());

	// 模拟分批接收数据
	parser.OnDataReceived(data.data(), 10); // 接收部分数据
	parser.OnDataReceived(data.data() + 10, data.size() - 10); // 接收剩余数据

	return 0;
}

#include <iostream>
#include <vector>
#include <cstring>

class TcpMessager 
{
	public:

		TcpMessager() :buffer(), expectedBodyLength(0) {}

		void OnDataReceived(const char *data, size_t length)
		{
			buffer.insert(buffer.end(), data, data + length);
			
			while (true)
			{
				if (expectedBodyLength == 0)
				{
					if (buffer.size() >= sizeof(uint32_t))
					{
						uint32_t BodyLength = ReadUint32(&buffer[0]);
						expectedBodyLength = BodyLength;

						buffer.erase(buffer.begin(), buffer.begin() + sizeof(uint32_t));
					}
					else
					{
						break;
					}
				}
				//检查是否可以读取消息体
				if (buffer.size() >= expectedBodyLength)
				{
					std::string messageBody(buffer.begin(), buffer.begin() + expectedBodyLength);
					HandleMessage(messageBody);

					buffer.erase(buffer.begin(), buffer.begin() + expectedBodyLength);

					expectedBodyLength = 0;
				}
				else
				{
					// 缓冲区数据不足,无法解析消息体
					break;
				}
			}
		}
private:
	// 缓冲区
	std::vector<char> buffer;

	// 当前期望的消息体长度
	uint32_t expectedBodyLength;

	// 从缓冲区中读取 4 字节小端整数
	uint32_t ReadUint32(const char* data) const
	{
		uint32_t value = 0;
		memcpy(&value, data, sizeof(uint32_t));
		return value;
	}

	void HandleMessage(const std::string& message) 
	{
		//std::cout << "收到消息: " << message << std::endl;
	}
};

TCP粘包、拆包的解决方式

为了解决TCP粘包、拆包的问题,我们可以采用以下几种方式:

1.定包长

定长包指的是在发送数据时,将每个数据包的长度固定为一个固定的值。接收方在接收数据时,根据固定的长度进行数据的解析。这种方式简单直观,但是由于数据的长度可能不是固定的,因此在实际应用中并不常见。

//拆包
        //1400 = mtu1518 - 18 - 20 - 40 = 1440 预留40
        //当前代码设置大于1400就开始拆包
        byte[] packByte = new byte[1400];
        int i = 0;
        int pageNum = inData.length; //数据包的大小
        init();//初始化函数,如果初始化函数传输到了服务器端,可以设置数据包防止粘包
        while (pageNum > 0){//如果数据包大于0进入循环
            if (pageNum > 1400){ //大于1400循环调用update发送数据
                System.arraycopy(inData, i * 1400, packByte, 0, 1400);
                update(packByte, 1400);
                pageNum -= 1400;
                i++;
            }else { //最后,调用一次update函数
                byte[] packByte1 = new byte[pageNum];
                System.arraycopy(inData, i * 1400, packByte1, 0, pageNum);
                update(packByte1, pageNum);
                pageNum -= pageNum;
            }
        }
        final();

2.分隔符包

分隔符包指的据包,接收方无法区分这些数据包,导致数据解析错误。

举报

相关推荐

0 条评论