网络编程-socket黏包现象

黏包现象:多个包粘黏到一起,即这次收到的结果还是上一次的结果

bug1:服务端在回复数据时采用了“+”号

bug2:客户端指定接收1024字节

黏包产生原理

  • 不管是recv还是send都不是直接接收对方数据,而是操作系统内存,不是一个send对应一个recv
  • recv:wait data耗时非常长,send:copy data

黏包问题只出现在TCP中,UDP中没有此现象

系统会将数据量小的且间隔时间断的通过算法合并到一起发送

简单方法解决黏包问题:

发送问题之前,先将数据信息发送给对方,让对方知道如何接收数据。

自定义定长的数据报头,对方再接收定长数据

补充知识:数据打包

import struct
#打包数据
'l'模式对打包数据无规定
res=struct.pack('i',11111) print(res,type(res),len(res)) #解包数据 obj=struct.unpack('i',res) print(obj)

服务端:

import socket
import subprocess
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True:
    conn,client_addr=phone.accept()
    print(client_addr)
    while True:
        try:
            #1、接收命令
            cmd=conn.recv(1024)
            if not cmd: break
            #2、执行命令并拿到结果
            obj=subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            #第一步:制作固定长度报头
            total_size=len(stdout)+len(stderr)
            header=struct.pack('i',total_size)
            #第二步,发送报头
            conn.send(header)
            #b、再发送真实数据
            conn.send(stdout+stderr)#“+”号需要优化
        except ConnectionResetError:
            break
    conn.close()#连接关闭
phone.close()#套接字关闭
View Code
  1. 制作固定长度报头
  2. 将报头发送给客户端
  3. 再发送真实数据

客户端:

import socket
import struct
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
#通信循环
while True:
    cmd=input('>> ').strip()
    if not cmd:continue#避免用户输入空字符串导致客户端卡死
    client.send(cmd.encode('utf-8'))
    #1、先拿到数据长度(包头),取得有效数据
    header=client.recv(4)
    total_size=struct.unpack('i',header)[0]
    recv_size=0
    recv_data=b''
    #直到在循环中接收完才退出循环,再次接收数据
    while recv_size < total_size:
        res=client.recv(1024)
        recv_data+=res
        recv_size+=len(res)
    print(recv_data.decode('gbk'))
#关闭连接
client.close()
View Code
  1. 先收报头
  2. 从报头中解析出对真实数据的藐视信息(数据长度)
  3. 接收真实数据

终极方法解决黏包问题:

server:

  1. 自定义一个字典形式的报头,包含文件所有信息
  2. 用struct模块将文件信息打包成固定长度的报头
  3. 发送固定长度的文件信息
  4. 发送真实数据
    import socket,os
    import subprocess
    import struct
    import json
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',8080))
    phone.listen(5)
    while True:
        conn,client_addr=phone.accept()
        print(client_addr)
        while True:
            try:
                #1、接收命令
                cmd=conn.recv(1024)
                if not cmd: break
                #2、执行命令并拿到结果
                obj=subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
                #第一步:制作固定长度报头
                total_size=len(stdout)+len(stderr)
                header_dic={'filename':"a.txt",
                            'md5':'xxxxxxx',
                            'total_size':len(stdout)+len(stderr)}
                header_json=json.dumps(header_dic)
                header_bytes=header_json.encode('utf-8')
                header=struct.pack('i',len(header_bytes))
                #第二步,先发送报头的长度
                conn.send(header)
                #第三步,发送报头
                conn.send(header_bytes)
                #第四步,发送真实数据
                conn.send(stdout+stderr)#“+”号需要优化
            except ConnectionResetError:
                break
        conn.close()#连接关闭
    phone.close()#套接字关闭
    View Code

client:

  1. 接收固定长度的报头
  2. 从报头中得知文件信息报文大小
  3. 获取文件信息数据
  4. 从文件信息数据中获取真实数据大小
  5. 接收真实数据
    import socket
    # client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # client.connect(('127.0.0.1',8080))
    # client.send('hello'.encode('gbk'))
    # client.send('world'.encode('gbk'))
    # # while True:
    # #     #1、发送命令
    # #     cmd=input('>> ').strip()
    # #     if not cmd:continue
    # #     client.send(cmd.encode('gbk'))
    # #     #2、拿到结果并打印
    # #     data=client.recv(1024).decode('gbk')#1024是坑
    # #     print(data)
    # client.close()
    import socket
    import struct
    import json
    client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('127.0.0.1',8080))
    #通信循环
    while True:
        cmd=input('>> ').strip()
        if not cmd:continue#避免用户输入空字符串导致客户端卡死
        client.send(cmd.encode('utf-8'))
        #1、先拿到数据长度(包头),取得有效数据
        header=client.recv(4)
        header_size=struct.unpack('i',header)[0]
        #2、接收报头
        header_bytes=client.recv(header_size)
        #3、从报头中获取有用数据
        header_json=header_bytes.decode('utf-8')
        header_dic=json.loads(header_json)
        print(header_json)
        total_size=header_dic['total_size']
        recv_size=0
        recv_data=b''
        #直到在循环中接收完才退出循环,再次接收数据
        while recv_size < total_size:
            res=client.recv(1024)
            recv_data+=res
            recv_size+=len(res)
        print(recv_data.decode('gbk'))
    #关闭连接
    client.close()
    View Code

猜你喜欢

转载自www.cnblogs.com/yaya625202/p/8949302.html