• 黏包


    黏包现象.

    • socket缓冲区

      • img

      • 每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

        write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。

        TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

        read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。

      • 缓冲区特性

        • I/O缓冲区在每个TCP套接字中单独存在

        • I/O缓冲区在创建套接字时自动生成

        • 即是关闭套接字也会继续传送输出缓冲区中遗留的数据

        • 关闭套接字将丢失输入缓冲区中的数据

        • 缓冲区默认大小8K,可以通过setsocket()函数获取:

        • import socket
          server = socket.socket()
          server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 重用ip地址和端口
          server.bind(('127.0.0.1',8010))
          server.listen(3)
          print(server.getsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF))  # 输出缓冲区大小
          print(server.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF))  # 输入缓冲区大小
          
          
    • 只有TCP有黏包现象,DUP永远不会黏包

      • 黏包问题主要还是因为接受方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的
    • 黏包产生情况

      • 接受方没有及时接收缓冲区的包,造成多个包接受(客户端发生了一段数据,服务端只收了一部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生黏包)
      • 发送端需要等缓冲区满才发送出去,造成黏包(发送数据时间间隔很短,数据也很小,会合到一起,产生黏包)
    • 黏包解决方法

      • 利用struct模块.将一个类型转成固定长度的bytes

        • img

        • import struct
          # 将一个数字转化成等长度的bytes类型。
          ret = struct.pack('i', 183346)
          print(ret, type(ret), len(ret))
          
          # 通过unpack反解回来
          ret1 = struct.unpack('i',ret)[0]
          print(ret1, type(ret1), len(ret1))
          
          
          # 但是通过struct 处理不能处理太大
          
          ret = struct.pack('l', 4323241232132324)
          print(ret, type(ret), len(ret))  # 报错
          

      方法1:low版

      • 将要发送的字节流总数按照固定的字节发送给接收端,然后再将数据发送过去,在用死循环将数据读完.

      • 服务端

        • import struct
          phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
          
          phone.bind(('127.0.0.1', 8080))
          
          phone.listen(5)
          
          while 1:
              conn, client_addr = phone.accept()
              print(client_addr)
              
              while 1:
                  try:
                      cmd = conn.recv(1024)
                      ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                      correct_msg = ret.stdout.read()
                      error_msg = ret.stderr.read()
                      
                      # 1 制作固定报头
                      total_size = len(correct_msg) + len(error_msg)
                      header = struct.pack('i', total_size)
                      
                      # 2 发送报头
                      conn.send(header)
                      
                      # 发送真实数据:
                      conn.send(correct_msg)
                      conn.send(error_msg)
                  except ConnectionResetError:
                      break
          
          conn.close()
          phone.close()
          
          
          # 但是low版本有问题:
          # 1,报头不只有总数据大小,而是还应该有MD5数据,文件名等等一些数据。
          # 2,通过struct模块直接数据处理,不能处理太大。
          
      • 客户端:

        • import socket
          import struct
          phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
          
          phone.connect(('127.0.0.1',8080))
          
          
          while 1:
              cmd = input('>>>').strip()
              if not cmd: continue
              phone.send(cmd.encode('utf-8'))
              
              # 1,接收固定报头
              header = phone.recv(4)
              
              # 2,解析报头
              total_size = struct.unpack('i', header)[0]
              
              # 3,根据报头信息,接收真实数据
              recv_size = 0
              res = b''
              
              while recv_size < total_size:
                  
                  recv_data = phone.recv(1024)
                  res += recv_data
                  recv_size += len(recv_data)
          
              print(res.decode('gbk'))
          
          phone.close()
          
      • 旗舰版:自定制报头

        • 整体流程

          • 整个流程的大致解释:
            我们可以把报头做成字典,字典里包含将要发送的真实数据的描述信息(大小啊之类的),然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。
            我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的,看一下
            
            发送时:
            先发报头长度
            再编码报头内容然后发送
            最后发真实内容
            
            接收时:
            先手报头长度,用struct取出来
            根据取出的长度收取报头内容,然后解码,反序列化
            从反序列化的结果中取出待取数据的描述信息,然后去取真实的数据内容
            
        • 服务端

        • import socket
          import subprocess
          import struct
          import json
          phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
          
          phone.bind(('127.0.0.1', 8080))
          
          phone.listen(5)
          
          while 1:
              conn, client_addr = phone.accept()
              print(client_addr)
              
              while 1:
                  try:
                      cmd = conn.recv(1024)
                      ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                      correct_msg = ret.stdout.read()
                      error_msg = ret.stderr.read()
                      
                      # 1 制作固定报头
                      total_size = len(correct_msg) + len(error_msg)
                      
                      header_dict = {
                          'md5': 'fdsaf2143254f',
                          'file_name': 'f1.txt',
                          'total_size':total_size,
                      }
                      
                      header_dict_json = json.dumps(header_dict) # str
                      bytes_headers = header_dict_json.encode('utf-8')
                      
                      header_size = len(bytes_headers)
                      
                      header = struct.pack('i', header_size)
                      
                      # 2 发送报头长度
                      conn.send(header)
                      
                      # 3 发送报头
                      conn.send(bytes_headers)
                      
                      # 4 发送真实数据:
                      conn.send(correct_msg)
                      conn.send(error_msg)
                  except ConnectionResetError:
                      break
          
          conn.close()
          phone.close()
          
        • 客户端

          • import socket
            import struct
            import json
            phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
            
            phone.connect(('127.0.0.1',8080))
            
            
            while 1:
                cmd = input('>>>').strip()
                if not cmd: continue
                phone.send(cmd.encode('utf-8'))
                
                # 1,接收固定报头
                header_size = struct.unpack('i', phone.recv(4))[0]
                
                # 2,解析报头长度
                header_bytes = phone.recv(header_size)
                
                header_dict = json.loads(header_bytes.decode('utf-8'))
                
                # 3,收取报头
                total_size = header_dict['total_size']
                
                # 3,根据报头信息,接收真实数据
                recv_size = 0
                res = b''
                
                while recv_size < total_size:
                    
                    recv_data = phone.recv(1024)
                    res += recv_data
                    recv_size += len(recv_data)
            
                print(res.decode('gbk'))
            
            phone.close()
            
  • 相关阅读:
    Objective-C NSFileManager 文件管理总结
    ScrollView在RelativeLayout失效问题
    解决myeclipse中struts2 bug问题包的替换问题
    SOA究竟是个啥
    Flash--元件和实例
    MyEclipse中加入web项目到tomcat
    [C]if (CONDITION)语句中CONDITION的情况
    GTK经常使用控件之笔记本控件( GtkNotebook )
    org.apache.solr.handler.dataimport.DataImportHandlerException: Data Config problem: 对实体 &quot;characterEn
    Android自动测试之Monkey工具
  • 原文地址:https://www.cnblogs.com/W-Y-C/p/11201724.html
Copyright © 2020-2023  润新知