Python之网络编程(三)TCP通信循环和粘包现象

socket补充:TCP通信循环和粘包现象

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

    • 基于TCP协议的socket的简单通信
    #server端
    
    # 网络通信与打电话(诺基亚)是一样的。
    
    import socket
    
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 买电话
    
    phone.bind(('127.0.0.1',8080))  # 0 ~ 65535  1024之前系统分配好的端口 绑定电话卡
    
    phone.listen(5)  # 同一时刻有5个请求,但是可以有N多个链接。 开机。
    
    
    conn, client_addr = phone.accept()  # 接电话
    print(conn, client_addr, sep='
    ')
    
    from_client_data = conn.recv(1024)  # 一次接收的最大限制  bytes
    print(from_client_data.decode('utf-8'))
    
    conn.send(from_client_data.upper())
    
    conn.close()  # 挂电话
    
    phone.close() # 关机
    
    #client 端
    
    import socket
    
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 买电话
    
    phone.connect(('127.0.0.1',8080))  # 与客户端建立连接, 拨号
    
    phone.send('hello'.encode('utf-8'))
    
    from_server_data = phone.recv(1024)
    
    print(from_server_data)
    
    phone.close()  # 挂电话
    
    • 基于TCP协议socket的循环通信:
    #server 端
    import socket
    
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#买手机
    
    phone.bind(('127.0.0.1',8080))#绑定手机卡
    
    phone.listen(5)#开机 5代表最多挂起5个 也可以好多个
    
    
    conn, client_addr = phone.accept()#等待接电话(conn是建立的连接,客户端的ip和端口号组成的元组)
    print(conn, client_addr, sep='
    ')
    
    while 1:  # 循环收发消息
        try:#如果不加try...except ,就会报错,因为它不知道你什么时候断开链接的,服务器还以为你在运行
            from_client_data = conn.recv(1024)#收了1024个字节的消息
            print(from_client_data.decode('utf-8'))
        
            conn.send(from_client_data + b'SB')
        
        except ConnectionResetError:#因为你不知道客户端什么时候断开链接
            break
    
    conn.close()#挂电话
    phone.close()#关机
    # 处理逻辑错误的两种方式:
         # if 判断
         # try...except 异常处理
     # 异常处理
     # 当你知道直接错误的条件时就用if判断了
     # 当程序错误一定发生,但是你又预知不了它出错的条件是什么的时候,就用try...except
    
    
    #客户端
    import socket
    
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 买电话
    
    phone.connect(('127.0.0.1',8080))  # 与客户端建立连接, 拨号
    
    
    while 1:  # 循环收发消息
        client_data = input('>>>')
        phone.send(client_data.encode('utf-8'))
        
        from_server_data = phone.recv(1024)
        
        print(from_server_data.decode('utf-8'))
    
    phone.close()  # 挂电话
    
    • 基于TCP协议链接循环通信
    #server 端
    import socket
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    phone.bind(('127.0.0.1',8848))
    
    phone.listen(3)
    
    while 1:# 循环连接客户端
        conn,addr=phone.accept()
        print(conn,addr)
        while 1:
            try:
                from_client_data=conn.recv(1024)
                if from_client_data.decode('utf-8').upper()=="Q":
                    print("客户端退出聊天")
                    break
                print(f'来自客户端{addr}的消息:{from_client_data.decode("utf-8")}')
                to_client_data=input(">>>").strip().encode("utf-8")
                conn.send(to_client_data)
            except ConnectionResetError:
                print("客户端断开连接")
                break
        conn.close()
    phone.close()
    
    #客户端
    import socket
    
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    phone.connect(('127.0.0.1',8848))
    
    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()=="Q":
                break
            from_server_data=phone.recv(1024)
            print(f'来自服务器消息:{from_server_data.decode("utf-8")}')
    phone.close()
    
    • 基于TCP协议的socket远程操控
    #server端
    import socket
    import subprocess
    phone=socket.socket()#买手机
    phone.bind(('127.0.0.1',8848))#绑定手机卡
    phone.listen(3)#阻塞的最大个数
    while 1:
        conn,addr=phone.accept()#等待连接
        print(conn,addr)
        while 1:
            try:
                from_client_data=conn.recv(1024)#接收的最大值
                if from_client_data.decode('utf-8').upper()=="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()
                conn.send(result)
            except ConnectionResetError:
                print("客户端退出聊天")
                break
        conn.close()
    phone.close()
    
    #客户端:
    
      import socket 
      phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      phone.connect(('192.168.20.44',8081))#绑定端口
      while True:
          cmd=input('>>请输入').strip()
          if  not cmd: continue
          phone.send(cmd.encode('utf-8'))
         data=phone.recv(10240)
         print('返回的是%s'%data.decode('gbk'))
     phone.close()
    
  2. 什么叫粘包现象?为什么会出现 粘包现象

    1. socket收发消息原理:

      • 应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。

        而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?

        可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

      • 设置缓冲区的两个好处:

        1. 暂时存储一些数据.

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

          所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

          只有TCP有粘包现象,UDP永远不会粘包

    2. 粘包的两种情况

      1. 接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

        • server端:

          import socket
          import subprocess
          
          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()
                      conn.send(correct_msg + error_msg)
                  except ConnectionResetError:
                      break
          
          conn.close()
          phone.close()
          
        • client端:

          import socket
          
          phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 买电话
          
          phone.connect(('127.0.0.1',8080))  # 与客户端建立连接, 拨号
          
          
          while 1:
              cmd = input('>>>')
              phone.send(cmd.encode('utf-8'))
          
              from_server_data = phone.recv(1024)
          
              print(from_server_data.decode('gbk'))
          
          phone.close()
          
      2. 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)

        • sever端:

          import socket
          
          
          phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
          
          phone.bind(('127.0.0.1', 8080))
          
          phone.listen(5)
          
          conn, client_addr = phone.accept()
          
          frist_data = conn.recv(1024)
          print('1:',frist_data.decode('utf-8'))  # 1: helloworld
          second_data = conn.recv(1024)
          print('2:',second_data.decode('utf-8'))
          
          
          conn.close()
          phone.close()
          
        • client端:

          import socket
          
          phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
          
          phone.connect(('127.0.0.1', 8080)) 
          
          phone.send(b'hello')
          phone.send(b'world')
          
          phone.close()  
          
          # 两次返送信息时间间隔太短,数据小,造成服务端一次收取
          
    3. 如何坚决粘包现象:

      1. 先介绍一下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))  # 报错
          
      2. 方案一:low版

        • 问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总数按照固定字节发送给接收端后面跟上总数据,然后接收端先接收固定字节的总字节流,再来一个死循环接收完所有数据。

        • server端:

          import socket
          import subprocess
          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()
          
        • client端:

          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()
          
原文地址:https://www.cnblogs.com/zhangdadayou/p/11431814.html