黏包

什么是黏包?

同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。

tcp黏包

服务器端
import socket
sk = socket.socket()

sk.bind(('127.0.0.1',8888))
sk.listen()

conn,addr = sk.accept()
while 1:
    conn.send(b'hello')
    conn.send(b'world')
conn.close()
sk.close()
客户端
import socket
sk = socket.socket()

sk.connect_ex(('127.0.0.1',8888))
while 1 :
    msg1 = sk.recv(1024)
    print('msg1:',msg1)

    msg2 = sk.recv(1024)

    print('msg2:',msg2)

sk.close()

UDP黏包

服务器
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',8899))

while 1:
    msg1 = sk.recvfrom(1024)
    print('msg1:', msg1)

    msg2 = sk.recvfrom(1024)
    print('msg2:', msg2)

sk.close()
客户端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)

while 1 :
    sk.sendto(b'hello',('127.0.0.1',8899))
    sk.sendto(b'world',('127.0.0.1',8899))

sk.close()

注意:只有TCP有粘包现象,UDP永远不会粘包

黏包成因

TCP协议中的数据传递

tcp协议的拆包机制:

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去.MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。

面向流的通信特点和Nagle算法

TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。
可靠黏包的tcp协议:tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

UDP不会发生黏包

UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。
不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。
不可靠不黏包的udp协议:udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y;x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。

补充说明:

用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) – UDP头(8)=65507字节。用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。(丢弃这个包,不进行发送)
    用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制(暂不考虑缓冲区的大小),这是指在用send函数时,数据长度参数不受限制。而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送。

总结:

粘包问题 :  只有tcp协议才会发送粘包,udp不会发

    EX:  发送端发送数据,接收端不知道应该如何接收,造成的一种数据混乱的现象

           tcp协议中,

一个合包机制(nagle算法),多次连续发送且间隔较小的数据,进行打包数据传送

有一个机制是拆包机制,发送端,因为受到网卡的MTU限制,将大的超过MTU限制的数据,进行拆分,拆分成多个小的数据,进行传输.  传输到目标主机的操作系统层时,重新将多个小的数据合并成原本的数据

针对 使用udp协议发送数据,一次收发大小究竟多少合适?

udp不会粘包,udp协议本层对一次收发数据大小的限制是:

    65535 - ip包头(20) - udp包头(8) = 65507

 

站在数据链路层,因为网卡的MTU一般被限制在了1500,所以对于数据链路层来说,一次收发数据的大小被限制在  1500 - ip包头(20) - udp包头(8) = 1472

 

得到结论:

    如果sendto(num)

 

     num > 65507  报错

     1472 < num < 65507  在数据链路层拆包,udp本身就是不可靠协议,所以一旦拆包之后,造成的多个小数据包在网络传输中,如果丢任何一个,那么此次数据传输失败

     num < 1472 比较理想的状态

 

posted @ 2018-08-14 17:57  chenyibai  阅读(217)  评论(0编辑  收藏  举报