先理解核心问题:什么是“TCP粘包”?
TCP 就像一条水管,数据通过水管从一端传到另一端。但它有个特点:不会按“发送时的小包”来划分,而是把数据当成连续的字节流。
比如:
- 你分两次发数据:第一次发“hello”(5字节),第二次发“world”(5字节);
- 接收方可能一次收到“helloworld”(10字节,两个包粘在一起),也可能第一次收到“hel”(3字节),第二次收到“loworld”(7字节)。
这种“数据没按预期分成小包,要么粘在一起,要么被拆成碎块”的情况,就叫 “粘包”。如果直接用普通的 read
函数,可能读不全或读多了,导致数据解析错误。
写个 Readn
函数:保证“读够指定的字节数”
不管数据是粘在一起还是被拆成碎块,Readn
都能确保你读到 “正好想要的字节数”。比如你要读10字节,它就一直等,直到凑够10字节才返回。
逐行说明:
ssize_t Readn(int fd, void* buf, size_t n)
// 参数:fd(要读的连接,比如客户端连接);buf(装数据的缓冲区);n(要读的字节数,比如10)
{size_t nleft = n; // 记录“还剩多少字节没读”(一开始等于n,比如10)while(nleft > 0) { // 只要还有没读完的,就继续循环// 调用系统的read函数,试着读剩下的字节(nleft)ssize_t nread = read(fd, buf, nleft);if(nread < 0) { // 读数据出错了if(errno == EINTR) continue; // 如果是“被信号打断”(比如系统临时有事),就重试return -1; // 其他错误(比如连接断了),返回-1表示失败}// 读到了nread字节(比如第一次读了3字节)buf = (char*)buf + nread; // 缓冲区指针往后移nread位(下次从新位置开始装)nleft -= nread; // 剩余字节数减少nread(比如10-3=7,还剩7字节要读)}return n; // 循环结束,说明读够了n字节,返回总字节数
}
举例说明:怎么解决粘包?
比如你要从客户端读10字节数据,可能遇到两种情况:
-
数据被拆成多段:
- 第一次
read
只读到3字节,nleft
变成7,继续循环; - 第二次
read
读到5字节,nleft
变成2,继续循环; - 第三次
read
读到2字节,nleft
变成0,循环结束,返回10(成功读够)。
- 第一次
-
数据粘在一起:
- 第一次
read
就读到15字节(比需要的10字节多),但Readn
只取前10字节,nleft
变成0,循环结束,剩下的5字节会留给下一次读取。
- 第一次
总结:
Readn
就像一个“执着的收快递员”:你说要10个包裹,它就一直等,哪怕快递分好几次到,也会凑齐10个再交给你,绝不会少给;如果快递多送了,就先收下需要的10个,剩下的下次再处理。
这样就能完美解决TCP粘包问题,确保程序读到的数据是完整、准确的,不会因为传输时的“拆包”“粘包”导致解析错误。