网络编程(3)粘包

两台电脑在进行收发数据时,其实不是直接将数据传输给对方。

  • 对于发送者,执行 sendall/send 发送消息时,是将数据先发送至自己网卡的 写缓冲区 ,再由缓冲区将数据发送给到对方网卡的读缓冲区。
  • 对于接受者,执行 recv 接收消息时,是从自己网卡的读缓冲区获取数据。

所以,如果发送者连续快速的发送了2条信息,接收者在读取时会认为这是1条信息,即:2个数据包粘在了一起。例如:

# socket客户端(发送者)
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8001))

client.sendall('hello'.encode('utf-8'))
client.sendall('world'.encode('utf-8'))

client.close()


# socket服务端(接收者)
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
conn, addr = sock.accept()

client_data = conn.recv(1024)
print(client_data.decode('utf-8'))

conn.close()
sock.close()

如何解决粘包的问题?

每次发送的消息时,都将消息划分为 头部(固定字节长度) 和 数据 两部分。例如:头部,用4个字节表示后面数据的长度。

  • 发送数据,先发送数据的长度,再发送数据(或拼接起来再发送)。
  • 接收数据,先读4个字节就可以知道自己这个数据包中的数据长度,再根据长度读取到数据。

对于头部需要一个数字并固定为4个字节,这个功能可以借助python的struct包来实现:

import struct

# ########### 数值转换为固定4个字节,四个字节的范围 -2147483648 <= number <= 2147483647  ###########
v1 = struct.pack('i', 199)
print(v1)  # b'\xc7\x00\x00\x00'

for item in v1:
    print(item, bin(item))

# ########### 4个字节转换为数字 ###########
v2 = struct.unpack('i', v1) # v1= b'\xc7\x00\x00\x00'
print(v2) # (199,)

示例代码:

  • 服务端

    import socket
    import struct
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('127.0.0.1', 8001))
    sock.listen(5)
    conn, addr = sock.accept()
    
    # 固定读取4字节
    header1 = conn.recv(4)
    data_length1 = struct.unpack('i', header1)[0] # 数据字节长度 21
    has_recv_len = 0
    data1 = b""
    while True:
        length = data_length1 - has_recv_len
        if length > 1024:
            lth = 1024
    	else:
            lth = length
    	chunk = conn.recv(lth) # 可能一次收不完,自己可以计算长度再次使用recv收取,指导收完为止。 1024*8 = 8196
        data1 += chunk
        has_recv_len += len(chunk)
        if has_recv_len == data_length1:
            break
    print(data1.decode('utf-8'))
    
    # 固定读取4字节
    header2 = conn.recv(4)
    data_length2 = struct.unpack('i', header2)[0] # 数据字节长度
    data2 = conn.recv(data_length2) # 长度
    print(data2.decode('utf-8'))
    
    conn.close()
    sock.close()
    
  • 客户端

    import socket
    import struct
    
    client = socket.socket()
    client.connect(('127.0.0.1', 8001))
    
    # 第一条数据
    data1 = 'hello'.encode('utf-8')
    
    header1 = struct.pack('i', len(data1))
    
    client.sendall(header1)
    client.sendall(data1)
    
    # 第二条数据
    data2 = 'world'.encode('utf-8')
    header2 = struct.pack('i', len(data2))
    client.sendall(header2)
    client.sendall(data2)
    
    client.close()
    
posted @ 2021-11-20 13:56  下个ID见  阅读(15)  评论(0)    收藏  举报