在互联网通信中,TCP是一种可靠的传输协议。它将数据分成多个小的数据包进行传输,并在接收端重新组装这些数据包,以确保数据的完整性和正确性。然而,由于网络传输的复杂性,TCP在传输过程中可能会出现粘包(Packet Sticking)和拆包(Packet Splitting)的
TCP粘包的原因和表现
TCP粘包指的是发送方在发送数据时,将多个逻辑上独立的数据包粘合在一起发送,导致接收方在接收时无法正确地区分这些数据包。造成TCP粘包的原因有多种,包括网络传输的延迟、缓冲区的限制、发送方的发送策略等。
TCP粘包的表现形式有两种:多个数据包粘合在一起,形成一个大的数据包。
- 多个数据包合并成一个数据包,但是在接收端无法正确地解析出每个数据包。
- 多个数据包合并成一个数据包,但是在接收端无法正确地解析出每个数据包。
TCP拆包的原因和表现
TCP拆包指的是发送方在发送数据时,将一个逻辑上独立的数据包拆分成多个小的数据包发送,导致接收方在接收时无法正确地组装这些数据包。TCP拆包的原因主要是由于发送方发送数据的速度过快,接收方处理数据的速度没有跟上
TCP拆包的表现形式有两种:
- 一个数据包被拆分成多个小的数据包,接收方无法正确地组装这些数据包。
- 一个数据包被拆分成多个小的数据包,但是在接收端可以正确地解析出每个数据包
代码实现
#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.分隔符包
分隔符包指的据包,接收方无法区分这些数据包,导致数据解析错误。