TCP粘包处理通用框架--C代码
说明:该文紧接上篇博文“
linux epoll机制对TCP 客户端和服务端的监听C代码通用框架实现
”讲来
(1)TCP粘包处理数据结构设计
#define MAX_MSG_LEN 65535 typedef struct { //当flag_in_NL_proc为1时,前面两个字段才有效 unsigned char g_recv_buff[2*MAX_MSG_LEN]; int g_recv_len;//前次累积未处理的TCP消息长度 int flag_in_NL_proc;//用于标记前次是否有不完整的TCP消息,1,表示有;0,表示没有;若有,当前待处理的TCP消息=前次累积未处理的TCP消息+当前利用recvfrom()接收的新TCP消息,若为0,则当前待处理的TCP消息即为新接收的TCP消息 }TCP_NL_MSG;
数据结构说明:
每个tcp连接维护一个TCP粘包处理结构体TCP_NL_MSG,代码可以维护一个全局变量map<int, TCP_NL_MSG> g_map_fd_TcpNLMsgStr(TCP socket和对应粘包处理结构体的映射表);
(2)粘包处理代码功能描述
RecvTcpMsg函数内部调用recvfrom接收tcp消息,
该函数功能描述:接收TCP消息(先进行粘包处理,然后根据消息类型进入不同的处理分支)
(3)粘包处理原理阐释
待补充;
(4)粘包处理的代码逻辑:
1 调用recvfrom()对本次epoll监听的socket可读事件进行读取到应用程序缓存curr_buff中;
2 判断该socket对应的TCP粘包处理结构体:p_tcp_nl_msg,判断p_tcp_nl_msg->flag_in_NL_proc标志是否为真:
2.1 若为真,则表明上次有未处理TCP消息缓存,保存在p_tcp_nl_msg->g_recv_buff指针中,长度为p_tcp_nl_msg->g_recv_len,则当前待处理的TCP消息为tcpmsg_tobe_processed = p_tcp_nl_msg->g_recv_buff+curr_buff,当前待处理的TCP消息长度为tcpmsglen_tobe_processed = p_tcp_nl_msg->g_recv_len+curr_buff.len;
然后将p_tcp_nl_msg->flag_in_NL_proc更新为0;
2.2 若为假,则表明上次没有未处理TCP消息缓存,则当前待处理的TCP消息即为tcpmsg_tobe_processed = curr_buff;
3 对tcpmsg_tobe_processed进行while(1)循环处理,循环体中的内容为:
从tcpmsg_tobe_processed中前面sizeof(TcpMsgHead)字节长度的消息为TCP消息头p_head,
首先比较tcpmsglen_tobe_processed 与sizeof(TcpMsgHead):
3.1 若前者小,即tcpmsglen_tobe_processed < sizeof(TcpMsgHead),意味着当前应用层接收的TCP消息长度小于TCP消息头的长度,则本次不解析,将tcpmsg_tobe_processed保存在p_tcp_nl_msg- >g_recv_buff指针中,然后将p_tcp_nl_msg->flag_in_NL_proc更新为1,并退出while循环;
3.2 若后者小,即tcpmsglen_tobe_processed > sizeof(TcpMsgHead), 则可以解析出应用层完整的tcp消息头长度为p_head->len,
再比较p_head->len和tcpmsglen_tobe_processed:
3.2.1 若p_head->len > tcpmsglen_tobe_processed,则表明应用层完整的tcp消息尚未接收完全,处理同3.1,退出while循环;
3.2.2 若p_head->len <= tcpmsglen_tobe_processed,则表明应用层完整的tcp消息已经接收完全,则tcpmsg_tobe_processed中前面p_head->len长度的字节即为来自于对端应用层的一条完整的TCP消息,对该消息调用ProcessTcpMsg()进行业务处理(功能是:根据不同的消息类型(维护在TCP消息头的type字段中)进入不同的业务处理分支,switch...case...);
然后对tcpmsg_tobe_processed从p_head->len字节到tcpmsglen_tobe_processed字节的一段消息更新到tcpmsg_tobe_processed中,即:tcpmsg_tobe_processed=tcpmsg_tobe_processed+p_head->len,将tcpmsglen_tobe_processed更新为:tcpmsglen_tobe_processed -= p_head->len,至此,进入while(1)循环的下一次处理,该分支的循环终止条件为:p_head->len == tcpmsglen_tobe_processed;
(5)粘包处理代码实现:
代码实现如下:
#define MAX_MSG_LEN 65535 map<int, TcpNlMsg> g_map_fd_TcpNLMsg; void RecvTcpMsg(int fd) { map<int, TcpNlMsg>::iterator iter = g_map_fd_TcpNLMsg.find(fd); if (iter == g_map_fd_TcpNLMsg.end()) { return; } TcpNlMsg* p_tcp_nl_msg = &iter->second; uint8_t recvbuf[MAX_MSG_LEN]; memset(recvbuf,0,MAX_MSG_LEN); struct sockaddr_in src_addr; socklen_t addrlen = sizeof(struct sockaddr_in);
//代码逻辑流程1 int recvlen = recvfrom(fd, recvbuf, MAX_MSG_LEN, 0, (struct sockaddr *) &src_addr, &addrlen); if(recvlen == 0) { printf("uehandle_recv_pack() receive shutdown command from peer endpoint\n") return; } if (recvlen == -1) { printf("uehandle_recv_pack() ret -1,errno=%d"\n, errno); return; }
//代码逻辑流程2
//代码逻辑流程2.1 if (p_tcp_nl_msg->flag_tcp_NL_proc == 1) { memcpy(p_tcp_nl_msg->g_recvbuf+p_tcp_nl_msg->g_recvlen,recvbuf,recvlen); recvlen += p_tcp_nl_msg->g_recvlen; memcpy(recvbuf, p_tcp_nl_msg->g_recvbuf, recvlen); p_tcp_nl_msg->flag_tcp_NL_proc = 0; } uint8_t* buf = recvbuf;
//代码逻辑3 while (1) {
//代码逻辑3.2 if (recvlen >= sizeof(TcpMsgHead)) { TcpMsgHead *head=(TcpMsgHead*)buf; uint32_t msglen = head->msglen; int type = head->msgtpe;
//代码逻辑流程3.2.2 if(recvlen >= msglen) { ProcessTcpMsg(buf+sizeof(TcpMsgHead), type);//业务处理函数,switch...case语句,根据不同的消息类型进入不同的处理分支 if (recvlen == msglen)//循环终止条件之一:当前待处理TCP消息恰好为一条完整的应用层消息 { break; } printf("recvlen(%u) > msglen(%u)\n", recvlen, msglen);
//更新待处理TCP消息缓存和长度,进入下一次while循环 recvlen -= msglen; buf += msglen; }
//代码逻辑流程3.2.1 else if(recvlen < msglen) { printf("sizeof(TcpMsgHead):%u < recvlen(%u) < msglen(%u)",sizeof(TcpMsgHead), recvlen, msglen); memset(p_tcp_nl_msg->g_recvbuf, 0, MAX_MSG_LEN); memcpy(p_tcp_nl_msg->g_recvbuf, buf, recvlen); p_tcp_nl_msg->g_recvlen = recvlen; p_tcp_nl_msg->flag_tcp_NL_proc = 1; break; } }
//代码逻辑流程3.1 else { printf("recvlen(%u) < sizeof(TcpMsgHead):%u\n", recvlen, sizeof(TcpMsgHead)); memset(p_tcp_nl_msg->g_recvbuf, 0, MAX_MSG_LEN); memcpy(p_tcp_nl_msg->g_recvbuf, buf, recvlen); p_tcp_nl_msg->g_recvlen = recvlen; p_tcp_nl_msg->flag_tcp_NL_proc = 1; break; } } }