• subprocess模块, 粘包问题及解决, 基于TCP协议上传大文件, UDP, socketserver模块


    subprocess模块

    作用

    • 通过代码执行操作系统的终端命令
    • 返回终端执行命令后的结果
    import subprocess  # 子进程模块
    
    cmd = input('请输入cmd命令:')
    
    # 实例化子进程对象
    obj = subprocess.Popen(
        # cmd命令
        cmd,
        # Shell = True
        shell=True,
        # 返回正确结果参数
        stdout=subprocess.PIPE,
        # 返回错误结果参数
        stderr=subprocess.PIPE
    )
    
    # 调用方法获取二进制正确输出结果和二进制错误信息结果并将它们合并
    res = obj.stdout.read() + obj.stderr.read()
    print(res.decode('gbk'))  # 二进制终端执行结果解码并打印
    

    小练习

    服务端

    # 服务器收到命令后执行,无论执行是否成功,无论执行几遍,都将执行结果返回给客户端
    import subprocess
    import socket
    
    server = socket.socket()
    server.bind(('127.0.0.1', 8888))
    
    server.listen()
    
    new_server_link, address = server.accept()
    
    while True:
        cmd = new_server_link.recv(1024).decode('utf8')
    
        # 执行cmd命令
        obj = subprocess.Popen(
            # cmd命令
            cmd,
            # Shell = True
            shell=True,
            # 返回正确结果参数
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        # 获取执行cmd命令的二进制数据结果
        res = obj.stdout.read() + obj.stderr.read()
    
        new_server_link.send(res)
    

    客户端

    import socket
    
    client = socket.socket()
    
    client.connect(('127.0.0.1', 8888))
    
    while True:
        cmd = input('请输入cmd命令:')
    
        client.send(cmd.encode('utf8'))
    
        # 解码服务端执行cmd命令后的二进制数据结果
        res = client.recv(1024).decode('gbk')
    
        print(res)
    

    粘包问题

    什么是粘包问题

    • 服务端第一次发送数据,客户端接收数据时无法预测数据的大小长度,从而无法一次性接收完所有数据
    • 服务端第二次发送数据时会将客户端第一次未接收完的数据一起发送,导致两次数据粘在一起

    TCP流式协议特点

    • TCP是一个流式协议,会将多次连续发送的数据量较小,并且时间间隔较短的数据一次性打包发送

      • 服务端

        import socket
        
        server = socket.socket()
        server.bind(('127.0.0.1', 8888))
        
        server.listen()
        
        new_server_link, address = server.accept()
        
        res1 = new_server_link.recv(1024).decode('utf8')
        res2 = new_server_link.recv(1024).decode('utf8')
        res3 = new_server_link.recv(1024).decode('utf8')
        
        print(f'res1:{res1}   ', f'res2:{res2}   ', f'res3:{res3}   ')
        
        # res1:hello    res2:hello    res3:hello
        # res1:hellohellohello    res2:    res3:
        
      • 客户端

        import socket
        import time
        client = socket.socket()
        
        client.connect(('127.0.0.1', 8888))
        
        
        client.send(b'hello')
        time.sleep(1)
        
        client.send(b'hello')
        time.sleep(1)
        
        client.send(b'hello')
        time.sleep(1)
        

    struct模块

    • 可以给一个很长数据,加上一个固定长度的标记(数据报头)

      import struct
      
      s = 'data'
      
      headers = struct.pack('i', 10000)  # i模式将数据报头压缩成4个bytes
      print(headers)
      print('*' * 20)
      print(len(headers))
      print('*' * 20)
      
      s_len = struct.unpack('i', headers)
      print(s_len)
      print('*' * 20)
      print(s_len[0])  # 获取真实数据长度
      
      '''
      b"x10'x00x00"
      ********************
      4
      ********************
      (10000,)
      ********************
      10000
      '''
      

    struct模块打包只包含数据长度的简单报头解决粘包问题

    先给要发送的数据打包制作报头,再发送报头,最后发送数据

    • 服务端

      import subprocess
      import socket
      import struct
      
      server = socket.socket()
      server.bind(('127.0.0.1', 8888))
      
      server.listen()
      
      new_server_link, address = server.accept()
      
      while True:
          cmd = new_server_link.recv(1024).decode('utf8')
      
          # 执行cmd命令
          obj = subprocess.Popen(
              # cmd命令
              cmd,
              # Shell = True
              shell=True,
              # 返回正确结果参数
              stdout=subprocess.PIPE,
              stderr=subprocess.PIPE
          )
          # 获取执行cmd命令的二进制数据结果
          res = obj.stdout.read() + obj.stderr.read()
      
          # 给res二进制数据打包制作报头
          res_header = struct.pack('i', len(res))
      
          # 先发送res二进制数据报头
          new_server_link.send(res_header)
      
          # 再发送res二进制数据
          new_server_link.send(res)
      
    • 客户端

      import socket
      import struct
      
      client = socket.socket()
      
      client.connect(('127.0.0.1', 8888))
      
      while True:
          cmd = input('请输入cmd命令:')
      
          client.send(cmd.encode('utf8'))
      
          # 接收res二进制数据的报头
          rec_headers = client.recv(4)
      
          # 将报头解包并获取res二进制数据的长度信息
          res_len = struct.unpack('i', rec_headers)[0]
      
          # 根据res二进制数据的长度动态分配相应大小的内存空间以接收res二进制数据
          res = client.recv(res_len)
      
          # 将res二进制数据解码并打印
          print(res.decode('gbk'))
      

    struct模块优化解决粘包问题

    • 服务端

      import json
      import socket
      import struct
      
      server = socket.socket()
      server.bind(('127.0.0.1', 8888))
      
      server.listen()
      
      new_server_link, address = server.accept()
      
      # 接收报头
      rec_header = new_server_link.recv(4)
      
      # 将报头解包获取数据长度
      res_len = struct.unpack('i', rec_header)[0]
      
      # 接收数据
      rec_json_bytes = new_server_link.recv(res_len)
      
      # 将数据解码
      res_json = rec_json_bytes.decode('utf-8')
      
      # 将数据反序列化
      res = json.loads(res_json)
      
      print(res)
      
    • 客户端

      import json
      import socket
      import struct
      
      client = socket.socket()
      
      client.connect(('127.0.0.1', 8888))
      
      # 制作需上传数据的信息字典
      data_dic = {
          'data_name': '高清无码两小时.avi',
          'data_size': 100000000
      }
      
      # 信息字典序列化文json格式
      data_dic_jason = json.dumps(data_dic)
      
      # 将jason格式的信息字典编码为二进制数据
      dic_jason_bytes = data_dic_jason.encode('utf-8')
      
      # 打包jason格式的信息字典的报头
      dic_jason_bytes_header = struct.pack('i', len(dic_jason_bytes))
      
      # 发送报头
      client.send(dic_jason_bytes_header)
      
      # 发送二进制的json格式的信息字典
      client.send(dic_jason_bytes)
      

    客户端基于TCP协议往服务端上传大文件

    服务端

    import socket
    import struct
    import json
    
    server = socket.socket()
    # 绑定ip地址和端口号
    server.bind(('127.0.0.1', 8888))
    # 服务端进入监听客户端请求状态
    server.listen()
    # 连接多个客户端
    while True:
        # 服务端接收客户端地址并创建新的特定的收发数据对象
        new_server_link, address = server.accept()
        # 异常断连处理
        try:
            while True:
                # 接收报头
                rec_header = new_server_link.recv(4)
                # 获取字典长度
                dic_len = struct.unpack('i', rec_header)[0]
                # 接收json格式字典数据
                rec_json_dic = new_server_link.recv(dic_len).decode('utf-8')
                # 反序列化
                rec_dic = json.loads(rec_json_dic)
                # 获取数据名和数据长度
                data_name = rec_dic.get('data_name')
                data_len = rec_dic.get('data_len')
                # 分段接收数据保存
                with open(data_name, 'wb') as fw:
                    for i in range(0, data_len, 1024):
                        data_split_rec = new_server_link.recv(1024)
                        fw.write(data_split_rec)
                    # 打印接收成功信息
                    print(f'{data_name}文件接收成功!')
        # 捕捉异常信息并打印
        except Exception as e:
            print(e)
    

    客户端

    import socket
    import struct
    import json
    
    client = socket.socket()
    # 发送连接请求
    client.connect(('127.0.0.1', 8888))
    # 文件路径
    file_path = r'C:Users龘Desktoppythonpython12期教师12期正课day 27视频9 socketserver补充.mp4'
    
    # 循环发送
    while True:
        # 上传判断代替用户输入文件名
        input('输入任意键确认传输:')
        # 读取文件数据
        with open(file_path, 'rb') as fr:
            data = fr.read()
        # 创建数据信息字典
        data_dic = {
            'data_name': '补充.mp4',  # 数据名
            'data_len': len(data)  # 数据长度
        }
        # 将数据字典序列化为json数据
        data_dic_json = json.dumps(data_dic)
        # 为json格式的信息字典创建报头
        dic_json_header = struct.pack(
            'i', len(data_dic_json)  # 字典长度
        )
        # 发送报头
        client.send(dic_json_header)
        # 发送数据字典信息
        client.send(data_dic_json.encode('utf-8'))
        # 分段发送数据
        with open(file_path, 'rb') as fr:
            for i in range(0, len(data), 1024):
                data_split = fr.read(1024)
                client.send(data_split)
                print(data_split, '*' * 1000)
            # 打印上传成功信息
            data_name = data_dic.get('data_name')
            print(f'{data_name}文件上传成功!')
    

    UDP协议

    什么是UDP

    • 是一种传输协议
    • 不需要建立双向管道
    • 不会粘包
    • 发出的数据不需要等待确认收到的消息,但是发送多少次数据就需要接收多少数据
    • 数据容易丢失,数据不安全

    UDP类似于发短信,TCP类似于打电话

    服务端

    import socket
    
    server = socket.socket(type=socket.SOCK_DGRAM)  # Datagram(数据报文)类型代表UDP协议
    
    # 服务端绑定ip+port
    server.bind(('127.0.0.1', 8888))
    
    msg1, address1 = server.recvfrom(1024)
    msg2, address2 = server.recvfrom(1024)
    msg3, address3 = server.recvfrom(1024)
    print(f'{address1}:{msg1}  {address2}:{msg2}  {address3}:{msg3}')
    
    # ('127.0.0.1', 53581):b'hello'  ('127.0.0.1', 53581):b'hello'  ('127.0.0.1', 53581):b'hello'
    

    客户端

    import socket
    
    client = socket.socket(type=socket.SOCK_DGRAM)
    
    server_ip_port = ('127.0.0.1', 8888)
    
    client.sendto(b'hello', server_ip_port)
    client.sendto(b'hello', server_ip_port)
    client.sendto(b'hello', server_ip_port)
    

    基于UDP协议实现QQ聊天室

    • 服务端

      import socket
      
      server = socket.socket(type=socket.SOCK_DGRAM)  # Datagram(数据报文)类型代表UDP协议
      
      # 服务端绑定ip+port
      server.bind(('127.0.0.1', 8888))
      
      while True:
          # 服务端接收多个客户端的消息和地址
          msg1, address1 = server.recvfrom(1024)
          msg2, address2 = server.recvfrom(1024)
          msg3, address3 = server.recvfrom(1024)
      
          print(msg1.decode('utf8'), address1)
          print(msg2.decode('utf8'), address2)
          print(msg3.decode('utf8'), address3)
      
          response = input('请输入回复:')
      
          # 服务端群发消息
          server.sendto(response.encode('utf8'), address1)
          server.sendto(response.encode('utf8'), address2)
          server.sendto(response.encode('utf8'), address3)
      
          if response == 'q':
              break
      
    • 客户端

      import socket
      
      client = socket.socket(type=socket.SOCK_DGRAM)
      
      server_ip_port = ('127.0.0.1', 8888)
      
      while True:
          msg = input('请输入消息:')
      
          client.sendto(msg.encode('utf8'), server_ip_port)
      
          response, _ = client.recvfrom(1024)
      
          print(response.decode('utf8'))
      

      socketserver模块

      • python内置模块
      • 可以简化TCP与UDP服务端代码
      • 必须要创建一个自定义的服务端协议类,该类必须继承请求处理基类,并重写请求处理基类中的handle方法

      服务端代码简化模板

      import socketserver
      
      
      class MyTcpServer(
          # 必须继承请求处理基类
          socketserver.BaseRequestHandler
      ):
          # 必须重写基类中的handle方法,客户端连接时会自动调用该方法创建request应答及获取客户端地址
      
          def handle(self):
              # 打印客户端地址
      
              try:  # 客户端异常断连处理
      
                  print(self.client_address)
      
                  while True:  # 循环收发数据
      
                      # 客户端接收数据
                      data = self.request.recv(1024).decode('utf8')  # request == new_server_link
      
                      # 客户端发送数据
                      response = data.upper()
                      self.request.send(response.encode('utf8'))  # request == new_server_link
      
              except Exception as e:
                  print(e)
      
      
      if __name__ == '__main__':
          # ThreadingTCPServer为服务端多线程处理类
          server = socketserver.ThreadingTCPServer(
              ('127.0.0.1', 8888),
              MyTcpServer
          )
      
          # 永久执行服务端
          server.serve_forever()
      
  • 相关阅读:
    「字符串算法」第4章 字典树课堂过关
    「字符串算法」第3章 KMP 算法课堂过关
    「字符串算法」第2章 Hash 和 Hash 表课堂过关
    「基础算法」第5章 广度搜索课堂过关
    「基础算法」第3章 二分算法课堂过关
    「基础算法」第1章 递推算法强化训练
    「基础算法」第1章 递推算法课堂过关
    YbtOJ:冲刺 NOIP2020 模拟赛 Day10
    【模板】轻重链剖分
    LINUX-磁盘空间
  • 原文地址:https://www.cnblogs.com/-406454833/p/11714614.html
Copyright © 2020-2023  润新知