1.分包和粘包
1.1 分包
发送方发送字符串 “helloworld” ,接收方却接收到了两个字符串 “hello” 和 “world”。
1.2 粘包
发送方发送两个字符串 “hello” 和 “world”,接收方却一次性接收到了 “helloworld”。
2.TCP是可靠传输
可靠传输能保证数据无差错、无丢失、按序和无重复的交付。
1)顺序不变,例如发送方发送hello,接收方也一定顺序接收到hello,这个是TCP协议承诺的,因此这点成为我们解决分包和粘包问题的关键.
2) 分割的包中间不会插入其他数据。
在实际开发中,为了解决分包和粘包的问题,就一定要自定义一份协议,最常用的方法是:
报文长度 + 报文内容
其实这个方法就是,告诉系统,我发送的报文内容的长度是多少,你接收这些就行了。
3)报文长度为 4 字节的整数,表示的是报文内容的长度,而不是整个 TCP 报文的长度,整个 TCP 报文的长度是报文内容的长度 +4
报文长度是 4 字节的整数,即int ,是以二进制流的方式写入socket,而不是 ascii 码。
3.TcpWrite() 和 TcpRead()
解决分包和粘包的方法,就封装在了这两个函数里面。下面是两个函数中的一些参数。
sockfd:可用的socket连接。buffer:接收数据缓冲区的地址。
ibuflen:本次成功接收数据的字节数。
itimeout:接收等待超时的时间,单位:秒,缺省值是0-无限等待。
返回值:true-成功; false-失败,失败有两种情况:1)等待超时;2) socket连接已不可用
3.1 TcpWrite()
(1)函数的声明及参数
bool TcpWrite(const int sockfd,const char *buffer,const int ibuflne);
从已经准备好的socket中读取数据。
sockfd:已经准备好的socket连接。
buffer:接收数据缓冲区的地址。
n:本次接收数据的字节数。
(2)函数的实现
bool TcpWrite(const int sockfd,const char *buffer,const int ibuflne)
{
if (sockfd == -1) return false;
fd_set tmpfd;
FD_ZERO(&tmpfd) ;
FD_SET(sockfd,&tmpfd) ;
struct timeval timeout;
timeout.tv_sec = 5 ; timeout.tv_usec= 0;
if ( select(sockfd+1,0,&tmpfd,0,&timeout)<=0 ) return false;
int ilen=0;
//如果长度为0,就采用字符串的长度
if (ibuflen==O) ilen=strlen (buffer);
elseilen=ibuflen;
int ilenn=htonl(ilen); //转换为网络字节序。
char strTBuffer[ilen+4];
memset(strTBuffer,0, sizeof(strTBuffer));
memcpy(strTBuffer,&ilenn,4);
memcpy(strTBuffer+4, buffer, ilen) ;
if(Writen(sockfd, strTBuffer,ilen+4)==false) return false;
return true;
}
3.2 TcpRead()
(1)函数的声明及参数
bool TcpRead(const int sockfd,char *buffer,int *ibflen,const int itimeout);
向socket的对端发送数据。
sockfd:可用的socket连接。
buffer:待发送数据缓冲区的地址。
ibuflen:待发送数据的字节数,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
返回值:true——成功;false——失败,如果失败,表示socket连接已经不能用。
(2)函数的实现
bool TcpRead(const int sockfd, char *buffer, int *ibuflen, const int itimeout)
{
if (sockfd =-1)return false;
if(itimeout > 0)
fd_set tmpfd;
FD_ZERO(&tmpfd) ;
FD_SET(sockfd,&tmpfd);
struct timeval timeout;
timeout.tv_sec = itimeout ; timeout.tv_usec =0;
int i ;
if ( (i = select(sockfd+1,&tmpfd,0,0,&timeout))<=0 ) return false;
(*ibuflen)= 0;
if(Readn(sockfd,(char*)ibuflen, 4)==false) return false;
(*ibuflen)=ntohl(*ibuflen);//把网络字节序转换为主机字节序。
if (Readn(sockfd, buffer,(*ibuflen))== false)return false;
return true;
}