python---网络编程(标准版解决粘包问题:)

  1. 基于TCP协议的socket循环通信

    • client

      import socket
      
      # 买电话
      phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 默认基于TCP协议的socket
      
      # 拨号
      phone.connect(('192.168.14.142',8080))
      
      while 1:
      
          data = input('请输入:').strip()
          if not data:
              print('输入内容不能为空')
              continue
              # 如果你输入一个空字符,服务端会阻塞,
          phone.send(data.encode('utf-8'))
          if data.upper() == 'Q':
              print('客户端关闭')
              break
          else:
              from_server_data = phone.recv(1024)
      
              print(f'来自服务端的消息:{from_server_data.decode("utf-8")}')
      
      phone.close()  # 关闭phone
      
    • server

      import socket
      
      # 买电话
      phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 默认基于TCP协议的socket
      
      # 绑定电话卡
      phone.bind(('127.0.0.1',8080))  # 绑定ip地址和端口
      
      # 开机监听
      phone.listen(5)
      print('等待接收...')
      # 等待连接
      conn , addr = phone.accept()  # 阻塞
      
      while 1:
          try:
              from_client_data = conn.recv(1024)
              if from_client_data.decode('utf-8').upper() == "Q":
                  print('客户端已经关闭')
                  break
              print(f'来自客户端的消息:{from_client_data.decode("utf-8")}')
              while 1:
                  to_client_data = input('请输入:').strip()
                  if not to_client_data:
                      print('输入内容不能为空')
                      continue
                  else:
                      break
              conn.send(to_client_data.encode('utf-8'))
          except ConnectionAbortedError:
              print('客户端已经关闭!')
              break
      conn.close()  # 关闭conn
      phone.close()  # 关闭phone
      
  2. 基于TCP协议的socket 链接+循环 通信

    • client

      import socket
      
      # 买电话
      phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 默认基于TCP协议的socket
      
      # 拨号
      phone.connect(('127.0.0.1',8080))
      
      while 1:
      
          data = input('请输入:').strip()
          if not data:
              print('输入内容不能为空')
              continue
              # 如果你输入一个空字符,服务端会阻塞,
          phone.send(data.encode('utf-8'))
          if data.upper() == 'Q':
              print('客户端关闭')
              break
          else:
              from_server_data = phone.recv(1024)
      
              print(f'来自服务端的消息:{from_server_data.decode("utf-8")}')
      
      phone.close()  # 关闭phone
      
    • server

      import socket
      
      # 买电话
      phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 默认基于TCP协议的socket
      
      # 绑定电话卡
      phone.bind(('127.0.0.1',8080))  # 绑定ip地址和端口
      # 开机监听
      phone.listen(3)
      while 1:
          print('等待接收...')
          # 等待连接
          conn , addr = phone.accept()  # 阻塞
          while 1:
              try:
                  from_client_data = conn.recv(1024)
                  if from_client_data.decode('utf-8').upper() == "Q":
                      print('客户端已经关闭')
                      break
                  print(f'来自客户端的消息:{from_client_data.decode("utf-8")}')
                  while 1:
                      to_client_data = input('请输入:').strip()
                      if not to_client_data:
                          print('输入内容不能为空')
                          continue
                      else:
                          break
                  conn.send(to_client_data.encode('utf-8'))
              except ConnectionResetError:
                  print('客户端已经关闭!')
                  break
          conn.close()  # 关闭conn
      phone.close()  # 关闭phone
      
  3. 基于TCP议的socket通信: 实例: 远程执行命令

    • client

      import socket
      
      # 买电话
      phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 默认基于TCP协议的socket
      
      # 拨号
      phone.connect(('192.168.14.142',8080))
      
      while 1:
      
          data = input('请输入:').strip()
          if not data:
              print('输入内容不能为空')
              continue
              # 如果你输入一个空字符,服务端会阻塞,
          phone.send(data.encode('utf-8'))
          if data.upper() == 'Q':
              print('客户端关闭')
              break
          else:
              from_server_data = phone.recv(1024)
      
              print(f'{from_server_data.decode("gbk")}')
      
      phone.close()  # 关闭phone
      
    • server

      import socket
      import subprocess
      
      # 买电话
      phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 默认基于TCP协议的socket
      
      # 绑定电话卡
      phone.bind(('192.168.14.100',8080))  # 绑定ip地址和端口
      # 开机监听
      phone.listen(3)
      while 1:
          print('等待接收...')
          # 等待连接
          conn , addr = phone.accept()  # 阻塞
          while 1:
              try:
                  from_client_data = conn.recv(1024)
      
      
                  obj = subprocess.Popen(from_client_data.decode('utf-8'),# 操作命令的字符串
                                         shell=True,
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE,
                                         )
                  a = obj.stdout.read()  # 正确命令
                  b = obj.stderr.read()  # 错误命令
                  if from_client_data.decode('utf-8').upper() == "Q":
                      print('客户端已经关闭')
                      break
                  print(f'来自客户端的消息:{from_client_data.decode("utf-8")}')
                  conn.send(a+b)
      
              except ConnectionResetError:
                  print('客户端已经关闭!')
                  break
          conn.close()  # 关闭conn
      phone.close()  # 关闭phone
      
      
  4. 粘包现象

    img

    • 粘包的概念:

      粘包:多个数据包被连续存储于缓存区中,在对数据包进行读取时,由于无法接受的字节数是固定的,但是我们无法确定发送方的数据量,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

    • 须知:只有TCP有粘包现象,UDP永远不会粘包

    • 粘包情况有两种:一种是粘在一起的包都是完整的数据包,另一种情况是粘在一起的包有不完整的包。

  5. 操作系统的缓存区

    1. 为什么存在缓冲区??

      1. 暂时存储一些数据.
      2. 缓冲区存在如果你的网络波动,保证数据的收发稳定,匀速.

      缺点: 造成了粘包现象之一.

  6. 什么情况下出现粘包

    1. 出现粘包的情况
      1. 连续短暂的send多次(数据量很小),你的数据会统一发送出去.
    2. 深入研究收发
  7. 如何解决粘包现象

    解决粘包现象的思路:

    服务端发一次数据 10000字节,

    客户端接收数据时,循环接收,每次(至多)接收1024个字节,直至将所有的字节全部接收完毕.将接收的数据拼接在一起,最后解码.

    1. 遇到的问题: recv的次数无法确定.

      你发送总具体数据之前,先给我发一个总数据的长度:5000个字节。然后在发送总数据。

      客户端: 先接收一个长度。 5000个字节。

      然后我再循环recv 控制循环的条件就是只要你接受的数据< 5000 一直接收。

    2. 遇到的问题: 总数据的长度转化成的字节数不固定

      服务端:
      conn.send(total_size) 
      
      conn.send(result)
      total_size int类型
      
      
      客户端:
      total_size_bytes = phone.recv(4)
      total_size
      data = b''
      while len(data) < total_size:
      	data = data + phone.recv(1024)
      

      你要将total_size int类型转化成bytes类型才可以发送

      387 ---- > str(387) ‘387’ ---->bytes b’387’ 长度 3bytes

      4185 ----> str(4185) ‘4185’ ---->bytes b’4185’ 长度 4bytes

      18000------------------------------------------------------> 长度 5bytes

      我们要解决:

      将不固定长度的int类型转化成固定长度的bytes并且还可以翻转回来。

      struct模块:

      这个模块可以把一个类型,转换成固定长度的bytes

      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))  # 报错
      

      img

  8. low版解决粘包现象

    1. client
    import socket
    import struct
    phone = socket.socket()
    
    phone.connect(('127.0.0.1',8848))
    while 1:
        to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
        if not to_server_data:
            # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
            print('发送内容不能为空')
            continue
        phone.send(to_server_data)
        if to_server_data.upper() == b'Q':
            break
    
        # 1. 接收报头
        head_bytes = phone.recv(4)
        # 2. 反解报头
        total_size = struct.unpack('i',head_bytes)[0]
    
        total_data = b''
    
        while len(total_data) < total_size:
            total_data += phone.recv(1024)
    
        print(len(total_data))
        print(total_data.decode('gbk'))
    
    phone.close()
    
    

    2.server

    # 1. 粘包第一种: send的数据过大,大于对方recv的上限时,对方第二次recv时,会接收上一次没有recv完的剩余的数据。
    import socket
    import subprocess
    import struct
    phone = socket.socket()
    
    phone.bind(('127.0.0.1',8848))
    
    phone.listen(2)
    # listen: 2 允许有两个客户端加到半链接池,超过两个则会报错
    
    while 1:
        conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
        # print(f'链接来了: {conn,addr}')
    
        while 1:
            try:
    
                from_client_data = conn.recv(1024)  # 接收命令
    
    
                if from_client_data.upper() == b'Q':
                    print('客户端正常退出聊天了')
                    break
    
                obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                       shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE,
    
                                       )
                result = obj.stdout.read() + obj.stderr.read()
                total_size = len(result)
                print(f'总字节数:{total_size}')
    
                # 1. 制作固定长度的报头
                head_bytes = struct.pack('i',total_size)
    
                # 2. 发送固定长度的报头
                conn.send(head_bytes)
    
                # 3. 发送总数据
                conn.send(result)
            except ConnectionResetError:
                print('客户端链接中断了')
                break
        conn.close()
    phone.close()
    
    
    

标准版解决粘包问题:


  1. recv工作原理

    源码解释:
    Receive up to buffersize bytes from the socket.
    接收来自socket缓冲区的字节数据,
    For the optional flags argument, see the Unix manual.
    对于这些设置的参数,可以查看Unix手册。
    When no data is available, block untilat least one byte is available or until the remote end is closed.
    当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。
    When the remote end is closed and all data is read, return the empty string.
    关闭远程端并读取所有数据后,返回空字符串。
    
  2. 高大上版解决粘包方式(自定制包头)

    我们要制作固定的报头

    你现在有两段不固定长度的bytes类型,我们要固定的报头,所以

    1. 你获取不固定报头的长度
    2. 利用struct模块将不固定的长度转化成固定的字节数 4个字节
    3. 先发4个字节,在发报头数据,在发总数据

    下面是具体的实现:

    • client

      import socket
      import struct
      import json
      
      phone = socket.socket()
      
      phone.connect(('127.0.0.1',8080))
      while 1:
          to_server_data = input('请输入:').strip()
      
          if not to_server_data:
              print('输入内容不能为空')
              continue
      
          phone.send(to_server_data.encode('utf-8'))
      
          if to_server_data.upper() == b'Q':
              break
      
          head_bytes = phone.recv(4)
      	# 接收报头
          total_size = struct.unpack('i',head_bytes)[0]
      	# 获得字典的长度,由于struct的格式是一个tuple,第一个参数是数据的长度
          head_dic = phone.recv(total_size)
      	# 按照换算的长度值截取字典的bytes类型
          dic_data_json = json.loads(head_dic)
      	# 将字典的bytes类型转换成字典类型
          total_size1 = dic_data_json['data_size']
      	# 在获取字典中的文件的大小
          total_data = b''
      	# 定义一个空的bytes
          while len(total_data) < total_size1:
              # 对比这两个数据的长度
              total_data += phone.recv(1024)
              # 如果total_data的长度小于total_size的长度就继续接受
      
          print(len(total_data))
      
          print(total_data.decode('gbk'))
      	#输出
      phone.close()
      
      
      
    • server

      # 粘包第一种:send的数据过大,大于对方recv的上限,对方第二次recv时,
      # 会接受上一次没有recv完的剩余数据
      
      import socket
      import subprocess
      import struct
      import json
      
      phone = socket.socket()
      
      phone.bind(('127.0.0.1',8080))
      
      phone.listen(3)
      
      while 1:
          conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
      
          while 1:
              try:
                  from_client_data = conn.recv(1024)
      
                  if from_client_data == b'Q':
                      print('客户端退出连接')
                      break
      
                  obj = subprocess.Popen(from_client_data.decode('utf-8'),  # 操作命令的字符串
                                         shell = True,
                                         stdout = subprocess.PIPE,
                                         stderr = subprocess.PIPE
                                         )
                  result = obj.stdout.read() + obj.stderr.read()
                  # 计算文件的长度
                  total_size = len(result)
      
                  # 定义一个自定义报头,字典类型
                  head_data = {
                      'filename':'周道容',
                      'MD5':561321561315,
                      'data_size':total_size
                  }
                  print(f'总的字节数{total_size}')
      			# 将字典转换成bytes类型
                  head_data_json_bytes = json.dumps(head_data).encode('utf-8')
      			# 计算这个bytes的长度
                  len_head_data_json_bytes = len(head_data_json_bytes)
      			# 将这个长度值用struct方法制作成一个固定长度的报头
                  len_head_size = struct.pack('i',len_head_data_json_bytes)
                  # 发送这个报头
                  conn.send(len_head_size)
      			#发送这个字典
                  conn.send(head_data_json_bytes)
      			# 发送文件数据
                  conn.send(result)
      
              except ConnectionRefusedError:
                  print('客户端中断连接')
                  break
          conn.close()
      phone.close()
      
      

3.基于UDP协议的socket通信

  • client

    import socket
    client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    # 基于网络的UDP协议的socket
    
    while 1:
    
        to_server_data = input('>>>:').strip()
        client.sendto(to_server_data.encode('utf-8'),('127.0.0.1',8080))
        # 发送数据时前边写数据,后边写ip和端口,固定格式
        data,addr = client.recvfrom(1024)
        print(f'来自服务端{addr}消息:{data.decode("utf-8")}')
    
  • server

    import socket
    server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    
    server.bind(('192.168.14.100',8080))
    
    while 1:
        from_client_data = server.recvfrom(1024)
        # 接收的数据是以元祖的形式,这两个值第一个是数据,第二个是发送者的ip地址和端口
        print(f'接收到来自{from_client_data[1]}的消息:{from_client_data[0].decode("utf-8")}')
        to_client_data = input('>>>').strip().encode('utf-8')
        server.sendto(to_client_data,from_client_data[1])
    
    
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值