select模式的底层机制会很容易出现粘包和少包的问题,出现的原因大家可以参考一下我的博客http://t.csdn.cn/IaaUr
今天我们先来学习一下如何处理客户端的粘包问题,也就是服务端给客户端发送数据时出现粘包少包的问题
我们的解决方式主要就是通过第二消息缓冲区来暂时存储我们的服务端传来的数据,通过判断消息长度来实现消息拷贝与接收,下面我们来说一下具体的实现方式
首先我们需要一个接收缓冲区,由于我们的数据收发测试并不是很大,所以我们的接收缓冲区并不需要设置的特别大,然后我们需要将数据接收到接收缓冲区
//接收缓冲区
char szrecv[10240] = {};
int nlen = recv(csock, szrecv, 10240, 0);
上面的nlen返回值就是我们收到的数据长度,接下来就是解决粘包问题的关键部分,我们需要一个第二消息缓冲区和一个尾指针来指示尾部位置,第二消息缓冲区是用来存数据的需要大一点,而尾指针是用来干什么的呢?前面我们讲到我们需要判断消息长度来进行消息的拷贝和进一步接收,尾指针的作用就是判断我们数据的长度,首先,第一次判断要判断尾指针的长度是否大于等于我们的消息头长度,这个很好理解,如果大于等于我们的消息头长度就可以确定我们的第二缓冲区里面的数组已经包含了我们需要的消息,此时我们就能确定消息长度,然后我们进行第二次判断,判断尾指针的长都是否大于消息长度,如果大于消息长度,就可以进行处理了。
处理方式就比较简单了首先我们需要获得未处理消息的长度以便我们接收下一条消息,再通过处理函数来进行处理,这些都比较好理解。接下来,我们需要将第二缓冲区的剩余消息前移,来进行下一次处理,然后我们需要更新尾坐标,这时候我们记录的未处理消息长度就排上用场了。下面我们来贴一下代码。
//接收缓冲区
char szrecv[10240] = {};
//第二缓冲区 消息缓冲区
char msgrecv[102400] = {};
//消息尾部位置
int lastpos = 0;
int proc(SOCKET csock) {
int nlen = recv(csock, szrecv, 10240, 0);
//printf("nlen=%d\n", nlen);
//将数据从接收缓冲区拷贝消息缓冲区
memcpy(msgrecv+ lastpos, szrecv, nlen);
lastpos = lastpos + nlen;
//判断消息缓冲区的数据长度是否大于消息头长度
while(lastpos>=sizeof(dataheader)){
//这时知道消息长度
dataheader* header = (dataheader*)msgrecv;
//如果消息缓冲区长度大于消息长度
if (lastpos >= header->datalength) {
//剩余未处理消息缓冲区数据长度
int nsize = lastpos - header->datalength;
//处理网络消息
doit(header);
//将未处理数据的位置传到缓冲区头部
memcpy(msgrecv, msgrecv + header->datalength, nlen);
//更新尾坐标
lastpos = nsize;
}
else { break; }//剩余消息不属于消息缓冲区
}
上述代码中doit是一个简单的处理函数。
通过上述的步骤解决粘包问题之后,我们在本地启动服务端和客户端消息处理能力基本到达了1.8Gbps左右。后续还会继续更新c++高性能服务器。