• 网络编程-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
  • 相关阅读:
    Mina、Netty、Twisted一起学(七):公布/订阅(Publish/Subscribe)
    MySQL高可用之——keepalived+互为主从
    JS之BOM
    Mac下利用(xcode)安装git
    计算矩阵边缘元素之和
    什么是猴子补丁(monkey patch)
    协议支撑
    BZOJ 3727 PA2014 Final Zadanie 树形DP
    Linux cat命令
    iOS8新特性
  • 原文地址:https://www.cnblogs.com/yaya625202/p/8949302.html
Copyright © 2020-2023  润新知