socket粘包问题及解决方案

一、粘包问题

问题1: 无法确认对方发送过来数据的大小。

‘client.py'

import socket

client = socket.socket()

client.connect(
    ('127.0.0.1', 9000)
)

while True:

    cmd = input('客户端输入的内容: ')

    client.send(cmd.encode('utf-8'))

    data = client.recv(19190)
    print(len(data))
    print(data.decode('gbk'))

‘server.py'

import socket
import subprocess

server = socket.socket()
server.bind(('127.0.0.1',9000))
server.listen(5)

while True:
    conn,addr = server.accept()
    print(addr)
    while True:
        try:
            cmd = conn.recv(10)
            if len(cmd) == 0:
                continue
            cmd = cmd.decode('utf-8')  #utf8
            if cmd == 'q':
                break
            #调用subprocess连接终端,对终端进行操作,并获取操作后正确或错误的结果
            obj = subprocess.Popen(
                #cmd接受的是解码后的字符串
                cmd,shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            #结果交给result变量名
            result = obj.stdout.read()+obj.stderr.read()
            print(len(result))
            print(result.decode('gbk'))  #windows系统下默认编码gbk
            #将结果返回给客户端
            conn.send(result)

        except Exception as e:
            print(e)
            break
    conn.close()

问题2: 在发送数据间隔短并且数据量小的情况下,会将所有数据一次性发送。

‘client.py'

import socket

client = socket.socket()

client.connect(
    ('127.0.0.1', 9000)
)

client.send(b'hello')
client.send(b'hello')
client.send(b'hello')

‘server.py'

import socket

server = socket.socket()

server.bind(
    ('127.0.0.1', 9000)
)

server.listen(5)

conn, addr = server.accept()

data = conn.recv(5)
print(data)  # b'hello'

data = conn.recv(1024)
print(data)  # b'hello'

data = conn.recv(1024)
print(data)  # b'hello'

二、粘包问题的解决方案:

粘包问题的解决方案: 确认对方数据的大小。

这里需要用 struct模块

struct是什么?
是一个python内置的模块,它可以将固定长度的数据,打包成固定格式的长度。
固定格式:如 “ i ” 模式
i : 4

struct作用:
可以将真实数据,做成一个固定长度的报头,客户端发送给服务器,服务器可以接受报头,然后对报头进行解包,获取真实数据的长度,进行接收即可

import struct

data = b'1111111111111111'
print(len(data))  #16

#打包制作报头
header = struct.pack('i',len(data))
print(header)   #b'x10x00x00x00'
print(len(header))  #4

#解包获取真实数据长度 --->得到一个元组,元组中第一个值是真实数据的长度
res = struct.unpack('i',header)[0]
print(res)  #16

无论哪一端先发送数据

  • 客户端
    - 1) 先制作报头,并发送 (struct)
    - 2) 发送真实数据

  • 服务端:
    - 1) 接收报头,并解包获取 真实数据长度
    - 2) 根据真实数据长度 接收真实数据
    recv(真实数据长度)

简单版:
‘client.py'

import socket
import struct

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

while True:
    cmd = input('客户端输入的内容: ')
    cmd_bytes = cmd.encode('utf-8')

    header = struct.pack('i',len(cmd_bytes))    #做一个报头
    print(len(header))  #打印报头的长度
    client.send(header)   #发送报头
    client.send(cmd_bytes)      #待服务端确认长度后,发送真实数据长度
    
    headers = client.recv(4)    #接受服务端的报头
    data_len = struct.unpack('i',headers)[0]     #解包
    result = client.recv(data_len)  #接受服务器返回的真实数据的长度

    print('接受服务器返回的真实数据的长度',len(result))
    print(result.decode('gbk'))

‘server.py'

import socket
import subprocess
import struct

server = socket.socket()
server.bind(('127.0.0.1',9000))
server.listen(5)

while True:
    conn,addr = server.accept()
    print(addr)
    while True:
        try:
            header = conn.recv(10)    #获取客户端传过来的报头
            data_len = struct.unpack('i',header)[0]  #解包获取真实数据的长度
  		    cmd = conn.recv(data_len)       #准备接受真实数据

            if len(cmd) == 0:
                continue
            cmd = cmd.decode('utf-8')  
            if cmd == 'q':
                break
            #调用subprocess连接终端,对终端进行操作,并获取操作后正确或错误的结果
            obj = subprocess.Popen(
                #cmd接受的是解码后的字符串
                cmd,shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            result = obj.stdout.read()+obj.stderr.read() #获取结果
            print('发送给服务端返回的真实数据的长度', len(result))
            header = struct.pack('i', len(result)) #做报头
            print(len(header))
            conn.send(header)   #发送报头给客户端
            conn.send(result)     #将结果返回给客户端
         
        except Exception as e:
            print(e)
            break
    conn.close()

序列化版:
‘client.py'

import socket,json
import struct

client = socket.socket()
client.connect(('127.0.0.1',9000))
while True:
    movie_name = input('请输入上传的电影名字:')

    #伪装电影的真实数据
    movie = 1000000
    send_dic ={'movie_name':movie_name,
               'movie':movie}
    #序列化
    json = json.dumps(send_dic)
    print(json)
    print(json.encode('utf-8'))
    print(len(json.encode('utf-8')))
    json_bytes = json.encode('utf-8')

    #做一个报头
    header = struct.pack('i',len(json_bytes))
    #先发送报头
    client.send(header)
    #再发送真实数据
    client.send(json_bytes)

‘server.py'

import socket,json
import struct

server = socket.socket()
server.bind(('127.0.0.1',9000))
server.listen(5)

while True:
    conn,addr = server.accept()
    while True:
        try:
            #获取客户端传过来的报头
            header = conn.recv(4)
            #解包获取真实数据的长度
            json_len = struct.unpack('i',header)[0]
            #接受json(dic)的真实数据
            json_bytes_data = conn.recv(json_len)
            #将bytes类型数据转为json数据类型
            json_data = json_bytes_data.decode('utf-8')
            #反序列化   json--->dict
            back_dic = json.loads(json_data)
            print(back_dic)
            print(back_dic.get('movie'))

        except Exception as e:
            print(e)
            break
    conn.close()
原文地址:https://www.cnblogs.com/baohanblog/p/12143846.html