zoukankan      html  css  js  c++  java
  • 网络编程笔记(3)——解决黏包问题

    内容目录:

    解决黏包问题:

    • 提前发送文件的大小
    • 使用struct模块
    • 定制报文

    内容详细:

    1.发送之前先发文件大小

    优点:

    • 确定了到底要接收多大的数据
    • 要在文件中配置一个配置项:每次recv文件的大小,buffer = 4096(建议使用这个)
    • 当我们要发送大数据时,明确告知接收方要发送多大的数据,以便接收方能准确的接收到所有的数据
    • 多用在文件传输的过程中:
      • 大文件的传输,一定是按照字节读,每次读固定大小的字节
      • 传输的过程中,一边读一边传;接收端:一边收一边写入文件

    缺点:

    • 多了一次接收交互

    注意:

    • send 或 sendto超过一定范围的时候都会报错(设备默认的范围大小)
    • 写文件传输的时候必须考虑程序的内存管理
    # server端:
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8080))
    sk.listen()
    
    conn,addr = sk.accept()
    
    while True:
        cmd = input('>>>')
        if cmd == 'q':
            conn.send(b'q')
            break
        conn.send(cmd.encode('gbk'))
        num = conn.recv(1024).decode('utf-8')   #接收到了client端发过来的字节总数
        conn.send(b'ok')
        res = conn.recv(int(num)).decode('gbk') #获取了接收的总字节数,设置进要接收的值里面,就可以动态的感知要发送的数据了
        print(res)
    
    conn.close()
    sk.close()
    
    #Client端:
    import socket
    import subprocess
    
    sk = socket.socket()
    sk.connect(('127.0.0.1',8080))
    while True:
        cmd = sk.recv(1024).decode('gbk')   #因为Windows传输都为GBK编码的
        if cmd == 'q':
            break
        res = subprocess.Popen(cmd,shell=True,          #PIPE为管道/队列,只能取一次,取完就结束了
                               stdout=subprocess.PIPE,  #stdout为命令的输出
                               stderr=subprocess.PIPE)  #stderr为命令错误的提示信息
        std_out = res.stdout.read() #从队列中取出命令
        std_err = res.stderr.read() #从队列中取出错误命令信息
        sk.send(str(len(std_out)+len(std_err)).encode('utf-8'))  #计算要发送的字节总数并发送过去
        sk.recv(4096)       #一般不要超过这个数字,否则会超过设备内存
        sk.send(std_out)
        sk.send(std_err)
    sk.close()
    

    2.使用struct模块

    • 什么是固定长度的bytes

    • 为什么要固定长度bytes?

      • 因为发送数据前,先发数据的长度,接收方接收长度。如果固定长度的话就省略了一步收发数据长度的次数
      #发送端需要借助struct模块
      import struct
      ret = struct.pack('i',2048) #'i'代表int,就是即将要把一个数字转换成固定长度的bytes类型
      print(ret)
      
      #接收端:
      num = struct.unpack('i',ret)	#此时num为元组tuple类型
      print(num[0])	#获取数据长度2048
      #这样接收方只需要接收固定的长度就可以获取整个数据的长度了
      
      # 使用struct模块收发信息
      # Server端:
      import socket
      import struct
      sk = socket.socket()
      sk.bind(('127.0.0.1',8080))
      sk.listen()
      
      conn,addr = sk.accept()
      
      while True:
          cmd = input('>>>')
          if cmd == 'q':
              conn.send(b'q')
              break
          conn.send(cmd.encode('gbk'))
          num = conn.recv(4)   #接收到了client端发过来的4个字节数(二进制),不管多大始终是4个字节
          num = struct.unpack('i',num)[0]   #把4个字节数二进制unpack,得到总字节数:num
          res = conn.recv(int(num)).decode('gbk') #获取了接收的总字节数,设置进要接收的值里面,就可以动态的感知要发送的数据了
          print(res)
      
      conn.close()
      sk.close()
      
      # Client端:
      import socket
      import struct
      import subprocess
      
      sk = socket.socket()
      sk.connect(('127.0.0.1',8080))
      while True:
          cmd = sk.recv(1024).decode('gbk')   #因为Windows传输都为GBK编码的
          if cmd == 'q':
              break
          res = subprocess.Popen(cmd,shell=True,          #PIPE为管道/队列,只能取一次,取完就结束了
                                 stdout=subprocess.PIPE,  #stdout为命令的输出
                                 stderr=subprocess.PIPE)  #stderr为命令错误的提示信息
          std_out = res.stdout.read() #从队列中取出命令
          std_err = res.stderr.read() #从队列中取出错误命令信息
          len_num = len(std_out) + len(std_err)#计算要发送的字节总数并发送过去
          num_by = struct.pack('i',len_num)	#使用模块pack进去总字节数,发送过去的始终是4个字节
          sk.send(num_by)     #发送了4个字节
          sk.send(std_out)
          sk.send(std_err)
      sk.close()
      

    3.定制报文报头

    • 我们在网络上传输的所有数据,都叫数据包

    • 数据包里的所有数据,都叫报文

    • 报文里不只有发送的数据,还有ip地址,MAC地址,端口号

    • 所有的报文都有报头

    • 可以自定义报文头

      head = {'filename':'test','filesize':409600,'filetype':'txt','filepath':r'userin'}
      # 先发送报头的长度              #接收4个字节的报头长度
      # send(head)                   #根据这4个字节获取报头
      # send(file)发送数据文件        #从报头中获取filesize,然后设置大小接收文件
      
    • 例子:上传文件

      # Server端:
      # 实现一个大文件的上传或下载
      # 配置文件,IP地址,端口号
      import socket
      import struct
      import json
      sk = socket.socket()
      sk.bind(('127.0.0.1',8090))
      sk.listen()
      buffer = 1024       #注意,如果设置过大会设备缓存会出错,导致文件接收不完整
      
      conn,addr = sk.accept()
      #接收
      head_len = conn.recv(4)
      head_len = struct.unpack('i',head_len)[0]
      json_head = conn.recv(head_len).decode('utf-8')
      head = json.loads(json_head)
      filesize = head['filesize']
      
      with open(head['filename'],'wb')as f:
          while filesize:
              if filesize >= buffer:
                  content = conn.recv(buffer)
                  f.write(content)
                  filesize -= buffer
              else:
                  content = conn.recv(filesize)
                  f.write(content)
                  filesize = 0
              print('====>',len(content))
          print(filesize)
      conn.close()
      sk.close()
      print(head['filesize'])
      
      # Client端:
      # 发送端
      import os
      import json
      import struct
      import socket
      sk = socket.socket()
      sk.connect(('127.0.0.1',8090))
      buffer = 1024
      
      # 发送文件
      head = {'filename':'02 python fullstack s9day47-web框架的本质.mp4',
              'filepath':r'D:硬盘文件老男孩pythonPython全栈9期(第02部分):并发编程+数据库+前端3. 前端开发day47',
              'filesize':None}
      filepath = os.path.join(head['filepath'],head['filename'])
      filesize = os.path.getsize(filepath)
      head['filesize'] = filesize
      json_head = json.dumps(head)    #字典转成了字符串
      bytes_head = json_head.encode('utf-8')  #字符串转bytes
      
      #计算head的长度
      head_len = len(bytes_head)      #报头的长度
      pack_len = struct.pack('i',head_len)
      sk.send(pack_len)           #先发报头的长度
      sk.send(bytes_head)         #再发送bytes类型的报头
      with open(filepath,'rb')as f:
          while filesize:
              if filesize >= buffer:
                  content = f.read(buffer)        #每次读出来的内容
                  sk.send(content)
                  filesize -= buffer
              else:
                  content = f.read(filesize)
                  sk.send(content)
                  filesize = 0
      
      sk.close()
      
  • 相关阅读:
    域名和IP地址的关系通俗解释
    简单卷、跨区卷、带区卷、镜像卷和 RAID5的区别
    什么是网络端口
    Windows7 64 bit 下解决:检索 COM 类工厂中 CLSID 为 {0002450000000000C000000000000046} 的组件时失败
    SQL函数,收藏先。
    C#中抽象类和接口的区别(转)
    SQL数据库碎片检查DBCC SHOWCONTIG含义
    SQL锁表语句
    50种方法优化SQL Server
    简单工厂模式(转)
  • 原文地址:https://www.cnblogs.com/lynlearnde/p/13471642.html
Copyright © 2011-2022 走看看