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粘包处理的更多相关文章

  1. Socket编程(4)TCP粘包问题及解决方案

    ① TCP是个流协议,它存在粘包问题 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.T ...

  2. Netty(三)TCP粘包拆包处理

    tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...

  3. netty 解决TCP粘包与拆包问题(二)

    TCP以流的方式进行数据传输,上层应用协议为了对消息的区分,采用了以下几种方法. 1.消息固定长度 2.第一篇讲的回车换行符形式 3.以特殊字符作为消息结束符的形式 4.通过消息头中定义长度字段来标识 ...

  4. Netty的TCP粘包/拆包(源码二)

    假设客户端分别发送了两个数据包D1和D2给服务器,由于服务器端一次读取到的字节数是不确定的,所以可能发生四种情况: 1.服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包. 2.服 ...

  5. netty 解决TCP粘包与拆包问题(一)

    1.什么是TCP粘包与拆包 首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线.当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包.得不到我 ...

  6. TCP粘包/拆包问题

    无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包/拆包 TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河 ...

  7. tcp粘包问题(封包)

    tcp粘包分析     http://blog.csdn.net/zhangxinrun/article/details/6721495 解决TCP网络传输“粘包”问题(经典)       http: ...

  8. 1. Netty解决Tcp粘包拆包

    一. TCP粘包问题 实际发送的消息, 可能会被TCP拆分成很多数据包发送, 也可能把很多消息组合成一个数据包发送 粘包拆包发生的原因 (1) 应用程序一次写的字节大小超过socket发送缓冲区大小 ...

  9. TCP 粘包/拆包问题

    简介    TCP 是一个’流’协议,所谓流,就是没有界限的一串数据. 大家可以想想河里的流水,是连成一片的.期间并没有分界线, TCP 底层并不了解上层业务数据的具体含义 ,它会根据 TCP 缓冲区 ...

  10. TCP粘包/拆包问题的解决

    TCP粘包拆包问题 一个完整的包可能被TCP拆分成多个包,或多个小包封装成一个大的数据包发送. 解决策略 消息定长,如果不够,空位补空格 在包尾增加回车换行符进行分割,例如FTP协议 将消息分为消息头 ...

随机推荐

  1. day04 list tuple (补)

    今日内容: 1. 列表 2. 列表的增删改查 3. 列表的嵌套 4. 元组和元组嵌套 5. range 列表 列表: 能装对象的对象. 有顺序的(按照我们添加的顺序保存) 在代码中使用[]表示列表. ...

  2. Scrum立会报告+燃尽图 01

    此作业要求:[https://edu.cnblogs.com/campus/nenu/2018fall/homework/2190] 一.小组介绍 组长:王一可 组员:范靖旋,王硕,赵佳璐,范洪达,祁 ...

  3. Right-BICEP 测试四则运算程序

    测试方法:      Right-BICEP 测试计划: 1.边界测试是否正确 2.负数表示是否实现 3.是否有乘除法 4.是否可以选择题目数量 5.是否有输出方式 6.是否有括号 7.是否有重复查询 ...

  4. web.config详解(转载)

    该文为转载 原文地址:http://www.cnblogs.com/gaoweipeng/archive/2009/05/17/1458762.html 花了点时间整理了一下ASP.NET Web.c ...

  5. C#中委托的理解

    请注意,这只是个人关于C#中委托的一点点理解,参考了一些博客,如有不周之处,请指出,谢谢! 委托是一种函数指针,委托是方法的抽象,方法是委托的实例.委托是C#语言的一道坎,明白了委托才能算是C#真正入 ...

  6. VS2013安装及单元测试

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAIlCAIAAACBzLJwAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAE ...

  7. 201621123037 《Java程序设计》第10周学习总结

    作业10-异常 标签(空格分隔): Java 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2. 书面作业 本次PTA作业题集异常 1. 常用异常 结合题集题目7 ...

  8. 关于解决java读取excel文件遇空行抛空指针的问题 !

    关于解决java读取excel文件遇空行抛空指针的问题 ! package exceRead; import java.io.File; import java.io.FileInputStream; ...

  9. 转载免安装版mysql的配置

    解压到自定义目录,我这里演示的是D:\wamp\mysql\   复制根目录下的my-default.ini,改名为my.ini,my.ini用下面内容替换 #以下是复制内容,这行可不复制 [clie ...

  10. 【Linux 命令】- tail命令

    linux tail命令用途是依照要求将指定的文件的最后部分输出到标准设备,通常是终端,通俗讲来,就是把某个档案文件的最后几行显示到终端上,假设该档案有更新,tail会自己主动刷新,确保你看到最新的档 ...