TCP粘包处理
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,
因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,
然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,
由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,
在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
TCP粘包我总结了几种情况
tcp发送端发送三个包过来,tcp接收缓存区收到了这三个包,而用户的读写缓存区比这三个包的总大小还大,
此时数据是接受完全的,用户缓存区读到三个包需要分开,这是比较好处理的。
第二种情况是因为用户的接收缓存区比tcp接受缓存区大,或者比tcp目前接收到的总数据大,那么用户缓存区读到
的数据就是tcp接收缓存区的数据,这是第一种情况的特例,这种情况需要判断那些包接受完全,那些包没接受完全。
第三种情况是用户的接受缓存区比tcp接受缓存区要小,导致用户缓存区读到的数据是tcp接收缓存区
的一部分,这其中有完整的包,也有残缺的包。
第四种情况是第三种情况的一个特例,用户缓存区的数据是不完全的,只是tcp缓存区的一部分。
对应特别大的那种包。
我提倡的解决办法就是首先实现一套从tcp缓存区中读取数据的数据结构和算法,因为tcp是面向
字节流的,将tcp缓存区中的数据读到用户缓存区里,这里我简单叫做outstreambuffer和instreambuffer,
这两个结构一个用于向tcp写,一个用于从tcp读。把tcp缓存区的数据尽可能多的读出来,不要判断是否是
完整的包,保证tcp缓存区没数据,这样会减少tcp粘包几率。
第二部就是将读到的数据,也就是instreambuffer中的数据进行分割,我叫做切包,切出一个个完整的包,
剩余不完整的留着下次继续接收。
第三步服务器应用层接口从instreambuffer中读取切割好的完整的包进行逻辑处理。
所以为了处理粘包和切包,需要我们自己设计包头,我设计的包头是八字节的结构体,
包含四字节的包id和四字节的包长度,这个长度既可以表示包头+消息体的长度,
也可以表示后面消息体的长度。我设计的是表示后面消息体的长度。
而上面所说的instreambuffer和outstreambuffer用户可以自己设计实现,也可以
利用成熟的网络库,我用的是libevent中的bufferevent,bufferevent实现了类似
的instreambuffer和outstreambuffer。
我设计的服务器部分代码如下,感兴趣可以去git下载:
https://github.com/secondtonone1/smartserver
简单列举下接收端处理读数据的过程。
void NetWorkSystem::tcpread_cb(struct bufferevent *bev, void *ctx)
{
getSingleton().dealReadEvent(bev, ctx);
}
networksystem是单例模式,处理读事件。因为静态函数tcpread_cb是libevent
设计格式的回调处理函数,在静态函数中调用非静态函数,我采用了单例调用。
void NetWorkSystem::dealReadEvent(struct bufferevent *bev, void *ctx)
{ //
evutil_socket_t bufferfd = bufferevent_getfd(bev);
std::map<evutil_socket_t, TcpHandler *>::iterator tcpHandlerIter = m_mapTcpHandlers.find(bufferfd);
if(tcpHandlerIter != m_mapTcpHandlers.end())
{
tcpHandlerIter->second->dealReadEvent();
}
}
tcphandler是我设计的切包类,这里通过bufferfd找到对应的instream和outstream,从而处理里面的数据完成切包。
//处理读事件
void TcpHandler::dealReadEvent()
{
evbuffer * inputBuf = bufferevent_get_input(m_pBufferevent);
size_t inputLen = evbuffer_get_length(inputBuf); while(inputLen > )
{
//tcphandler第一次接收消息或者该node接收完消息,需要开辟新的node接受消息
if(!m_pLastNode || m_pLastNode->m_nMsgLen <= m_pLastNode->m_nOffSet)
{
//判断消息长度是否满足包头大小,不满足跳出
if(inputLen < PACKETHEADLEN)
{
break;
} char data[PACKETHEADLEN] = {};
bufferevent_read(m_pBufferevent, data, PACKETHEADLEN);
struct PacketHead packetHead; memcpy(&packetHead, data, PACKETHEADLEN); cout << "packetId is : " <<packetHead.packetID << endl; cout << "packetLen is : " << packetHead.packetLen << endl; insertNode(packetHead.packetID, packetHead.packetLen); inputLen -= PACKETHEADLEN;
} //考虑可能去包头后剩余的为0
if(inputLen <= )
{
break;
}
//读取去除包头后剩余消息
tcpRead(inputLen);
} }
这个函数判断是否读完一个消息,读完就开辟新的节点存储新来的消息,否则就将新来的消息放入没读完的节点里。
void TcpHandler::tcpRead(UInt32 &inputLen)
{
//node节点中的数据还有多少没读完
UInt32 remainLen = m_pLastNode->m_nMsgLen - m_pLastNode->m_nOffSet; UInt32 readLen = bufferevent_read(m_pBufferevent, m_pLastNode->m_pMsg + m_pLastNode->m_nOffSet, remainLen);
//统计bufferevent 的inputbuffer中剩余的长度
inputLen -= readLen;
//更改偏移标记
m_pLastNode->m_nOffSet += readLen;
//判断读完
if(m_pLastNode->m_nOffSet >= m_pLastNode->m_nMsgLen)
{
m_pLastNode->m_pMsg[m_pLastNode->m_nMsgLen + ] = '\0';
cout << "receive msg is : " << m_pLastNode->m_pMsg << endl;
//cout <<"read times is : " << ++readtimes<< endl;
} }
我的服务器还在完善中,目前已经能处理连续收到1万个包的切包和大并发的问题了,最近在设计应用层的序列化
和应用层消息回调。感兴趣可以下载看看,下载地址:https://github.com/secondtonone1/smartserver
我的微信公众号平台,谢谢关注:
TCP粘包处理的更多相关文章
- Socket编程(4)TCP粘包问题及解决方案
① TCP是个流协议,它存在粘包问题 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.T ...
- Netty(三)TCP粘包拆包处理
tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...
- netty 解决TCP粘包与拆包问题(二)
TCP以流的方式进行数据传输,上层应用协议为了对消息的区分,采用了以下几种方法. 1.消息固定长度 2.第一篇讲的回车换行符形式 3.以特殊字符作为消息结束符的形式 4.通过消息头中定义长度字段来标识 ...
- Netty的TCP粘包/拆包(源码二)
假设客户端分别发送了两个数据包D1和D2给服务器,由于服务器端一次读取到的字节数是不确定的,所以可能发生四种情况: 1.服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包. 2.服 ...
- netty 解决TCP粘包与拆包问题(一)
1.什么是TCP粘包与拆包 首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线.当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包.得不到我 ...
- TCP粘包/拆包问题
无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包/拆包 TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河 ...
- tcp粘包问题(封包)
tcp粘包分析 http://blog.csdn.net/zhangxinrun/article/details/6721495 解决TCP网络传输“粘包”问题(经典) http: ...
- 1. Netty解决Tcp粘包拆包
一. TCP粘包问题 实际发送的消息, 可能会被TCP拆分成很多数据包发送, 也可能把很多消息组合成一个数据包发送 粘包拆包发生的原因 (1) 应用程序一次写的字节大小超过socket发送缓冲区大小 ...
- TCP 粘包/拆包问题
简介 TCP 是一个’流’协议,所谓流,就是没有界限的一串数据. 大家可以想想河里的流水,是连成一片的.期间并没有分界线, TCP 底层并不了解上层业务数据的具体含义 ,它会根据 TCP 缓冲区 ...
- TCP粘包/拆包问题的解决
TCP粘包拆包问题 一个完整的包可能被TCP拆分成多个包,或多个小包封装成一个大的数据包发送. 解决策略 消息定长,如果不够,空位补空格 在包尾增加回车换行符进行分割,例如FTP协议 将消息分为消息头 ...
随机推荐
- [leetcode-884-Uncommon Words from Two Sentences]
We are given two sentences A and B. (A sentence is a string of space separated words. Each word co ...
- “Hello World!”团队第五周第一次会议
今天是我们团队“Hello World!”团队第五周召开的第一次会议,欢迎我们的新小伙伴刘耀泽同学.博客内容: 一.会议时间 二.会议地点 三.会议成员 四.会议内容 五.Todo List 六.会议 ...
- 定制自己的动画 View 控件(Canvas 使用)
定制自己的动画 View 控件(Canvas 使用) 如果要定义自己的 View 控件,则需要新建一个类继承 android.view.View.然后在 onDraw 中写自己需要实现的方式. 这里定 ...
- 0429团队项目-Scrum团队成立
Scrum团队成立 团队名称:开拓者 团队目标:努力让每一个小伙伴在学会走路的基础上学会跑. 团队口号:我们要的只是这片天而已. 团队照:正面照+背影照(那就是为什么组名叫开拓者) 5.2 角色分配 ...
- Qt使用QNetworkAccessManager实现Http操作
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt使用QNetworkAccessManager实现Http操作 本文地址:http ...
- 小程序解密 encryptedData 获取 unionID 等信息
index.php <?php include_once "wxBizDataCrypt.php"; // $appid 由小程序微信官方后台获取 $appid = 'wx4 ...
- Spring 中常用注解原理剖析
前言 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来配置 ...
- 第101天:CSS3中transform-style和perspective
一.transform-style 1.transform-style属性是3D空间一个重要属性,指定嵌套元素如何在3D空间中呈现. 有两个属性值:flat和preserve-3d. transfor ...
- AtCoder Regular Contest 083 D: Restoring Road Network
题意 有一张无向带权连通图(点数<=300),给出任意两点i,j之间的最短路长度dis[i][j].问是否存在一张这样的无向图.如果不存在输出-1.如果存在输出所有这样的无向图中边权和最小的一张 ...
- 洛谷 P4114 Qtree1
Qtree系列都跟树有着莫大的联系,这道题当然也不例外 我是题面 读完题,我们大概就知道了,这道题非常简单,可以说是模板题.树剖+线段树轻松解决 直接看代码吧 #include<algorith ...