Socket编程实践(5)

TCP粘包问题

由于TCP协议是基于字节流且无边界的传输协议,因此很有可能产生粘包问题,问题描述如下

对于HostA发送的M1与M2两个各10K的数据块,HostB接收数据的方式不确定,有以下方式接收:

先接收M1,再接收M2(正确方式)

先接收M2,再接收M1(错误)

一次性收到20k数据(错误)

分两次收到,第一次15k,第二次5k(错误)

分两次收到,第一次5k,第二次15k(错误)

其他任何可能(错误)

粘包产生的原因

1、SQ_SNDBUF套接字本身有缓冲区(发送缓冲区、接受缓冲区)

2、tcp传送的端mss大小限制

3、链路层也有MTU大小限制,如果数据包大于>MTU要在IP层进行分片,导致消息分割。

4、tcp的流量控制和拥塞控制,也可能导致粘包

5、tcp延迟发送机制等

TCP与UDP关于粘包问题的对比

TCP

UDP

字节流

数据报

无边界

有边界

对等方的一次读操作并不能保证完全把消息读完

对方接收数据包的个数是不确定的

粘包解决方案(本质上是要在应用层维护消息与消息的边界)

(1)定长包

该方式并不实用:如果所定义的长度过长,则会浪费网络带宽,而又如果定义的长度过短,则一条消息又会拆分成为多条,仅在TCP的应用一层就增加了合并的开销,何况在其他层(因此我在博客中并未给出定长包的示例,而是将之(一个不太完善的实现)与使用自定义报头的示例放到了一起,感兴趣的读者可以下载下来查看);

(2)包尾加\r\n(FTP使用方案)

如果消息本身含有\r\n字符,则也分不清消息的边界;

(3)报文长度+报文内容

(4)更复杂的应用层协议

readn/writen实现

Socket,管道以及某些设备(特别是终端和网络)有下列两种性质:

1)一次read操作所返回的数据可能少于所要求的数据,即使还没到达文件尾端也可能这样,但这不是一个错误,应当继续读该设备;

2)一次write操作的返回值也可能少于指定输入的字节数.这可能是由于某个因素造成的,如:内核缓冲区满…但这也不是一个错误,应当继续写余下的数据(通常,只有非阻塞描述符,或捕捉到一个信号时,才发生这种write的中途返回)

在读写磁盘文件时从未见到过这种情况,除非是文件系统用完了空间,或者接近了配额限制,不能将所要求写的数据全部写出!

通常,在读/写一个网络设备,管道或终端时,需要考虑这些特性.于是,我们就有了下面的这两个函数:readn和writen,功能分别是读/写指定的count字节数据,并处理返回值可能小于要求值的情况:

/**实现:这两个函数只是按需多次调用read和write系统调用直至读/写了count个数据**//**返回值说明:== count: 说明正确返回, 已经真正读取了count个字节== -1 : 读取出错返回< count: 读取到了末尾**/ssize_t readn(int fd, void *buf, size_t count){size_t nLeft = count;ssize_t nRead = 0;char *pBuf = (char *)buf;while (nLeft > 0){if ((nRead = read(fd, pBuf, nLeft)) < 0){//如果读取操作是被信号打断了, 则说明还可以继续读if (errno == EINTR)continue;//否则就是其他错误elsereturn -1;}//读取到末尾else if (nRead == 0)return count-nLeft;//正常读取nLeft -= nRead;pBuf += nRead;}return count;}/**返回值说明:== count: 说明正确返回, 已经真正写入了count个字节== -1 : 写入出错返回**/ssize_t writen(int fd, const void *buf, size_t count){size_t nLeft = count;ssize_t nWritten = 0;char *pBuf = (char *)buf;while (nLeft > 0){if ((nWritten = write(fd, pBuf, nLeft)) < 0){//如果写入操作是被信号打断了, 则说明还可以继续写入if (errno == EINTR)continue;//否则就是其他错误elsereturn -1;}//如果 ==0则说明是什么也没写入, 可以继续写else if (nWritten == 0)continue;//正常写入nLeft -= nWritten;pBuf += nWritten;}return count;}报文长度+报文内容实践

发报文时:前四个字节长度+报文内容一次性发送;

收报文时:先读前四个字节,求出报文内容长度;根据长度读数据。

始终调整好自己观风景的心态,

Socket编程实践(5)

相关文章:

你感兴趣的文章:

标签云: