TCP“粘包”问题
2022-09-05 本文已影响0人
牛奶言兼
接TCP链路,参考tcp/ip的相关资料,描述tcp数据包为无边界数据包协议,那么什么是无边界呢?
参考如下测试程序:
服务端示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <stdarg.h> #include "unistd.h" #include "netinet/in.h" // sockaddr_in #include "arpa/inet.h" // inet_pton #define MAXLINE 4096 #define LISTENQ 1024 void my_log(const char* file, int line, const char* fmt, ...) { va_list args; printf("file:%s, line:%d ", file, line); va_start(args, fmt); vprintf(fmt, args); va_end(args); printf("%s", "\n"); } #define LOG(fmt, ...) my_log(__FILE__, __LINE__, fmt, ##__VA_ARGS__); int main(int argc, char **argv) { LOG("==== server start ===="); int listenfd, connfd; struct sockaddr_in servaddr; char buff[MAXLINE]; time_t ticks; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(1024); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) { LOG("bind port success."); } else { LOG("bind port fail."); exit(-1); } listen(listenfd, LISTENQ); // 这里应该补充状态判断 LOG("wait recv connect request."); for(;;) { LOG("block net i/o."); connfd = accept(listenfd, (struct sockaddr *)NULL, NULL); // 服务端的accept会形成阻塞,这里也就涉及到了非阻塞模型,比如select、poll、epoll等API LOG("accept connect request."); for (int i = 0; i < 100; ++i) { ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); write(connfd, buff, strlen(buff)); LOG("send msg."); sleep(10); } close(connfd); // break; // 如果不想结束服务,注掉这里 } LOG("==== server end ===="); exit(0); }
客户端示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include "unistd.h" #include "netinet/in.h" // sockaddr_in #include "arpa/inet.h" // inet_pton #define MAXLINE 4096 void my_log(const char* file, int line, const char* fmt, ...) { va_list args; printf("file:%s, line:%d ", file, line); va_start(args, fmt); vprintf(fmt, args); va_end(args); printf("%s", "\n"); } #define LOG(fmt, ...) my_log(__FILE__, __LINE__, fmt, ##__VA_ARGS__); int main(int argc, char **argv) { LOG("==== client start ===="); int sockfd, n; char recvline[MAXLINE + 1]; struct sockaddr_in servaddr; if (argc != 2) { LOG("usage: a.out <IPaddress>"); exit(1); } if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { LOG("socket error"); exit(1); } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(1024); // 注意:需要确保跟服务端监听的端口一致 if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) { LOG("inet_pon error for %s", argv[1]); exit(1); } if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { // sock.h connect LOG("connect error"); exit(1); } sleep(20); // 等待一会儿,触发tcp粘包现象 while ((n = read(sockfd, recvline, MAXLINE)) > 0) { // 无差别读取内容 recvline[n] = 0; LOG("recv content:"); if (fputs(recvline, stdout) == EOF) { LOG("fputs error."); exit(1); } } if (n < 0) { LOG("read error"); exit(1); } LOG("==== client end ===="); exit(0); }
编译配置完成后,执行服务,等待客户端连接。连接成功后,执行一段时间,观察日志输出。
服务端日志
% ./server
file:server.c, line:28 ==== server start ====
file:server.c, line:41 bind port success.
file:server.c, line:50 wait recv connect request.
file:server.c, line:53 block net i/o.
file:server.c, line:55 accept connect request.
file:server.c, line:60 send msg.
file:server.c, line:60 send msg.
file:server.c, line:60 send msg.
file:server.c, line:60 send msg.
file:server.c, line:60 send msg.
file:server.c, line:60 send msg.
客户端日志
% ./client 127.0.0.1
file:client.c, line:26 ==== client start ====
file:client.c, line:58 recv content:
Tue Sep 6 09:53:46 2022
Tue Sep 6 09:53:51 2022
Tue Sep 6 09:53:56 2022
Tue Sep 6 09:54:01 2022
file:client.c, line:58 recv content:
Tue Sep 6 09:54:06 2022
file:client.c, line:58 recv content:
Tue Sep 6 09:54:11 2022
从客户端的日志可以看出,在连接成功后等待了20s的时间,足够服务端发送4条消息,当客户端开始读取socket套接字时,第一次读取时,直接读取到了4条消息,再随后,因为客户端是即时读取,则每次只有一条消息。
这就是tcp消息包的“粘包”问题,要解决此类问题,可以采用消息长度+消息负载的方式,或者更复杂的应用层控制。
什么是粘包,所谓粘包就是连续给对端发送两个或者两个以上的数据包,对端在一次收取中收到的数据包数量可能大于 1 个,当大于 1 个时,可能是几个(包括一个)包加上某个包的部分,或者干脆就是几个完整的包在一起。当然,也可能收到的数据只是一个包的部分,这种情况一般也叫半包。
衍生出一个问题
tcp的“粘包”消息中是一个完整的tcp消息流吗?还是可能是残缺的?