《Python》网络编程之黏包

黏包

一、黏包现象

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

  server端

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()

conn, addr = sk.accept()
conn.send(b'hello,')
conn.send(b'world')
conn.close()

  client端

import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 9000))

ret1 = sk.recv(1024)
print(ret1, len(ret1))  # b'hello,world' 11
ret2 = sk.recv(1024)
print(ret2, len(ret2))  # b'' 0
sk.close()

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

二、黏包成因

1、合包现象:

  数据很短

  时间间隔短

2、拆包现象:

  大数据会发生拆分

  不会一次性的全部发送到对方

  对方在接收的时候很可能没用办法一次性接收到所有的信息

  那么没有接收完的信息很可能和后面的信息黏在一起

3、黏包现象只发生在tcp协议

  tcp协议的传输是:流式传输

  每一条信息与信息之间是没有边界 

4、udp协议中是不会发生黏包现象的

  适合短数据的发送

  不建议发送过长的数据

  会增大数据丢失的几率

会发生黏包的两种情况:

1、发送方的缓存机制

  发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

2、接收方的缓存机制

  接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

总结:

 黏包现象只发生在tcp协议中:

  1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。

  2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

三、黏包的解决方案

1、解决方案:

  问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

  server端:

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()

conn, addr = sk.accept()
inp = input('>>>')
s = str(len(inp)).zfill(4) + inp  # 在输入的内容前面拼上它的长度(4位数,不足在前面用0补齐)
conn.send(s.encode('utf-8'))

conn.close()
'''
两个弊端:
    复杂
    最多也只能一次性传递9999个字节
'''

  client端:

import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 9000))

ret1 = sk.recv(4)   # 先接收前面的长度
num1 = int(ret1.decode('utf-8'))
ret = sk.recv(num1) # 再以长度接收
print(ret.decode('utf-8'))

sk.close()
存在的问题:
程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗

2、解决方案进阶

  我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。

struct模块:

  该模块可以把一个类型,如数字,转成固定长度的bytes

print(struct.pack('i',1111111111111))

struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

  借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

发送时 接收时
先发送struct转换好的数据长度4字节 先接受4个字节使用struct转换成数字来获取要接收的数据长度
再发送数据 再按照长度接收数据

server端:

import struct
import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()

conn, addr = sk.accept()
while 1:
    s = input('>>>').encode('utf-8')
    pack_num = struct.pack('i', len(s))
    conn.send(pack_num)
    conn.send(s)
conn.close()

client端:

import socket
import struct

sk = socket.socket()
sk.connect(('127.0.0.1', 9000))

while 1:
    pack_num = sk.recv(4)
    num = struct.unpack('i', pack_num)[0]
    ret = sk.recv(num)
    print(ret.decode('utf-8'))
sk.close()
原文地址:https://www.cnblogs.com/yzh2857/p/9648867.html