于是,我们有理由认为,send发送不完全的情况,虽然有可能发生,但可以不多去考虑,发生就直接closesocket,此来可以避免很多刁钻而繁琐发送、接收问题(同样的发送问题,也会再WSASend函数中遇到,IOCP模型中,这个问题尤为突出)。
如果考虑到send有可能发送不完全,那么处理起来就有点麻烦了,特殊情况下,我们无法否认send连一个4字节的包头都不能完整发送,从而导致recv端一次只收到1字节或两字节的数据,这时候去读包头长度字段就会读到脏数据,错误的认为这个包非常巨大,甚至长度为负数,所以我们不得不在处理的时候加以细致的判断。
简单起见,还是先只以阻塞套接字为模型寻找某种可能性,比如,假设对端提交了一个完整包,由TCP协议负责发送到本地,这种情况有没有可能导致recv读取不完全呢? 这个问题,可以参考一篇文章,地址是: http://www.cnitblog.com/donne/archive/2010/12/23/72500.html 文中有这么一段话,个人觉得其对socket编程最为有用,不过,由于原作者并没有认真组织语言,文章看起来有点磕,为了阅读性,这里尽量在不曲解本意的原则下,作了点修改: 如果调用socket函数send,阻塞发送大于1452字节的数据,那么发送端的IP协议层就会将数据分片,而接收端的IP协议层负责接收并重组数据,如果一个分片丢失,则整个TCP包都会重发。 也就是说,只要对端send没有出问题,本地recv申请读取的数据长度足够,还是可以得到完整数据包的。 想想看,只要对端send函数没出问题,ACK机制保证了数据能够完整的提交给上层,进入本地缓冲区,recv函数只是从中读取而已,那么只要请求读取的字节数足够,那么有什么理由只复制回来半个数据包? 缓冲区不足的话,一整个数据包都应该全丢了,读半个包是什么情况?
所以,综上所述,尾巴数据的问题,最极端的情况,是由对端send函数造成,带来的麻烦也最大,更普遍的情况,则是recv函数的接收缓冲区与系统缓冲区之间的问题,recv请求复制1000字节,系统缓冲区有1300字节,最后一个数据包500字节,那recv函数将1000字节复制过来,当然会把最后一个数据包会给拦腰斩断。
//如果不考虑send发送不完全的情况,接受缓冲区长度设置为4的整数倍,每个数据包长度也都是4的整数倍,连个包头都收不齐的情况应该可以避免
unsigned long _stdcall recv_func(void *arg) { SOCKET s=(SOCKET)arg; char *c_str=new char[2000]; char *c_cac=new char[2000]; int i_cac=0; while(TRUE) { int i_eax=::recv(s,c_str,2000,0); if(i_eax<=0) { ::printf("error(%d): can not recv data\n",::GetLastError()); break; } char *p=c_str; int i_ecx; if(i_cac>0) { i_ecx=*(int*)c_cac; int i_dis=i_ecx-i_cac; ::memcpy(c_cac+i_cac,p,i_dis); on_data(c_cac,i_ecx); p+=i_dis; i_eax-=i_dis; i_cac=0; } while(i_eax>0) { i_ecx=*(int*)p; if(i_ecx>i_eax) { ::memcpy(c_cac,p,i_eax); i_cac=i_eax; break; } on_data(p,i_ecx); p+=i_ecx; i_eax-=i_ecx; } } return 0; }
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
上面红字内容有严重错误,特更正如下:
recv端会因为某些不知名的原因,即便系统缓冲区内容足够,请求拷贝的数据长度也足够,还是会有数据拷贝不全的情况发生,这将导致若干次recv也未必能够凑成一个完整的数据包,于是,代码还需进一步修改,更正如下:
//c_cac:字符数组,缓存,用来存放上次没有处理完的数据,又称半包缓冲区
//i_cac:已经缓存下来的数据长度
unsigned long _stdcall recv_func(void *arg) { ...... char *c_str=new char[2000]; char *c_cac=new char[2000]; int i_cac=0; while(TRUE) { i_eax=::recv(s,c_str,2000,0); if(i_eax<4) { ::closesocket(s); break; } char *p=c_str; int i_ecx; int i_dis; if(i_cac>0) { if(i_cac<4) { i_dis=4-i_cac; ::memcpy(c_cac+i_cac,p,i_dis); i_cac=4; p+=i_dis; i_eax-=i_dis; } i_ecx=*(int*)c_cac; i_dis=i_ecx-i_cac; if(i_dis>i_eax) { ::memcpy(c_cac+i_cac,p,i_eax); i_cac+=i_eax; continue; } ::memcpy(c_cac+i_cac,p,i_dis); on_data(c_cac,i_ecx); p+=i_dis; i_eax-=i_dis; i_cac=0; } while(i_eax>0) { if(i_eax<4) { ::memcpy(c_cac,p,i_eax); i_cac=i_eax; break; } i_ecx=*(int*)p; if(i_ecx>i_eax) { ::memcpy(c_cac,p,i_eax); i_cac=i_eax; break; } on_data(p,i_ecx); p+=i_ecx; i_eax-=i_ecx; } } delete []c_cac; delete []c_str; return 0; }