zoukankan      html  css  js  c++  java
  • python-基于tcp协议的套接字(加强版)及粘包问题

    一、基于tcp协议的套接字(通信循环+链接循环)

    服务端应该遵循:

      1.绑定一个固定的ip和port

      2.一直对外提供服务,稳定运行

      3.能够支持并发

    基础版套接字:

    from socket import *
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    
    conn, client_addr = server.accept()
    
    # 通信循环
    while True:
        data = conn.recv(1024)
        conn.send(data.upper())
    
    conn.close()
    server.close()
    server
    from socket import *
    
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(('127.0.0.1', 8080))
    
    # 通信循环
    while True:
        msg=input('>>: ').strip()
        client.send(msg.encode('utf-8'))
        data=client.recv(1024)
        print(data)
    
    client.close()
    client

    以上的程序存在两个bug

      1.当客户端单方面终止程序时,服务端抛出异常(linux可以用判断是否为空来处理)

      2.recv收到空时,一直在等待。

    解决方法:

      1.异常捕获

      2.再输入时进行判断

    改进版:

    from socket import *
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1', 8082))
    server.listen(5)
    
    conn, client_addr = server.accept()
    print(client_addr)
    
    # 通信循环
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:break # 针对linux系统
            print('-->收到客户端的消息: ',data)
            conn.send(data.upper())
        except ConnectionResetError:
            break
    
    conn.close()
    server.close()
    server
    from socket import *
    
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(('127.0.0.1', 8081))
    
    # 通信循环
    while True:
        msg=input('>>: ').strip() #msg=''
        if len(msg) == 0:continue
        client.send(msg.encode('utf-8')) #client.send(b'')
        # print('has send')
        data=client.recv(1024)
        # print('has recv')
        print(data)
    
    client.close()
    client

    链接循环:服务器改进

    from socket import *
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1', 8082))
    server.listen(5)
    
    conn, client_addr = server.accept()
    print(client_addr)
    
    # 通信循环
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:break # 针对linux系统
            print('-->收到客户端的消息: ',data)
            conn.send(data.upper())
        except ConnectionResetError:
            break
    
    conn.close()
    server.close()
    server

    模拟ssh实现远程执行命令

    from socket import *
    import subprocess
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1', 8081))
    server.listen(5)
    
    # 链接循环
    while True:
        conn, client_addr = server.accept()
        print(client_addr)
    
        # 通信循环
        while True:
            try:
                cmd = conn.recv(1024) #cmd=b'dir'
                if len(cmd) == 0: break  # 针对linux系统
                obj=subprocess.Popen(cmd.decode('utf-8'),
                                 shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE
                                 )
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
                print(len(stdout) + len(stderr))
                conn.send(stdout+stderr)
            except ConnectionResetError:
                break
    
        conn.close()
    
    server.close()
    server
    from socket import *
    
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(('127.0.0.1', 8081))
    
    # 通信循环
    while True:
        cmd=input('>>: ').strip()
        if len(cmd) == 0:continue
        client.send(cmd.encode('utf-8'))
        cmd_res=client.recv(1024000)
        print(cmd_res.decode('gbk'))
    
    client.close()
    client

    recv其实是和本地计算机要数据,所以解码的时候应该用gbk格式

    二、粘包

    注:只有tcp存在粘包现象,udp永远不存在粘包。

    tcp是面向流的协议,发送端可以1k,1k的发送数据,而接收端可以2k,2k的提取数据,发送方往往收集到足够多的数据后才生成一个tcp段,若连续几次需要发送的数据都很少,通常tcp会根据(Nagle)优化算法,把这些数据合成一个TCP段后发出去,这样接收方就收到了粘包数据。

    总的来说,粘包问题就是因为接收方不知道消息之间的界限,不知道一次性提取多少字节造成的。

    解决方法:(服务端)为字节流加上一个报头,将这个报头(字典形式)json序列化,编码。然后用struct发送报头的长度,再发送报头,最后发送真实数据

         (客户端)先解出报头的长度(struct),再接收报头,将拿到的报头解码再反序列化,得到报头字典,最后接收真正的数据

    # 服务端必须满足至少三点:
    # 1. 绑定一个固定的ip和port
    # 2. 一直对外提供服务,稳定运行
    # 3. 能够支持并发
    from socket import *
    import subprocess
    import struct
    import json
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1', 8081))
    server.listen(5)
    
    # 链接循环
    while True:
        conn, client_addr = server.accept()
        print(client_addr)
    
        # 通信循环
        while True:
            try:
                cmd = conn.recv(1024)  # cmd=b'dir'
                if len(cmd) == 0: break  # 针对linux系统
                obj = subprocess.Popen(cmd.decode('utf-8'),
                                       shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE
                                       )
                stdout = obj.stdout.read()
                stderr = obj.stderr.read()
                # 1. 先制作报头
                header_dic = {
                    'filename': 'a.txt',
                    'md5': 'asdfasdf123123x1',
                    'total_size': len(stdout) + len(stderr)
                }
                header_json = json.dumps(header_dic)
                header_bytes = header_json.encode('utf-8')
    
                # 2. 先发送4个bytes(包含报头的长度)
                conn.send(struct.pack('i', len(header_bytes)))
                # 3  再发送报头
                conn.send(header_bytes)
    
                # 4. 最后发送真实的数据
                conn.send(stdout)
                conn.send(stderr)
            except ConnectionResetError:
                break
    
        conn.close()
    
    server.close()
    server
    from socket import *
    import struct
    import json
    
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(('127.0.0.1', 8081))
    
    # 通信循环
    while True:
        cmd=input('>>: ').strip()
        if len(cmd) == 0:continue
        client.send(cmd.encode('utf-8'))
        #1. 先收4bytes,解出报头的长度
        header_size=struct.unpack('i',client.recv(4))[0]
    
        #2. 再接收报头,拿到header_dic
        header_bytes=client.recv(header_size)
        header_json=header_bytes.decode('utf-8')
        header_dic=json.loads(header_json)
        print(header_dic)
        total_size=header_dic['total_size']
    
        #3. 接收真正的数据
        cmd_res=b''
        recv_size=0
        while recv_size < total_size:
            data=client.recv(1024)
            recv_size+=len(data)
            cmd_res+=data
    
        print(cmd_res.decode('gbk'))
    
    client.close()
    client

    补充:struct模块

    该模块可以帮一个类型,如数字,转成固定长度的bytes

    1、 struct.pack
    struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型,可以把这里的字符串理解为字节流,或字节数组)。其函数原型为:struct.pack(fmt, v1, v2, ...),参数fmt是格式字符串,关于格式字符串的相关信息在下面有所介绍。v1, v2, ...表示要转换的python值。

    2、 struct.unpack
    struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组。 

    import struct
    
    obj1=struct.pack('i',13321111)
    print(obj1,len(obj1))#b'x97Cxcbx00' 4
    
    res1=struct.unpack('i',obj1)
    print(res1[0])#13321111
    View Code
  • 相关阅读:
    Salesforce LWC学习(十二) Dependence Picklist实现
    Salesforce LWC学习(十一) port 1717报错的处理
    Salesforce LWC学习(十) 前端处理之 list 处理
    salesforce零基础学习(九十五)lightning out
    Salesforce LWC学习(九) Quick Action in LWC
    Salesforce LWC学习(八) Look Up组件实现
    第四百零一节,Django+Xadmin打造上线标准的在线教育平台—生产环境部署virtualenv虚拟环境安装,与Python虚拟环境批量安装模块
    第四百节,Django+Xadmin打造上线标准的在线教育平台—生产环境部署CentOS6.5安装python3.5.1
    第三百九十九节,Django+Xadmin打造上线标准的在线教育平台—生产环境部署CentOS6.5安装mysql5.6
    第三百九十八节,Django+Xadmin打造上线标准的在线教育平台—生产环境部署CentOS6.5系统环境设置
  • 原文地址:https://www.cnblogs.com/mangM/p/9580645.html
Copyright © 2011-2022 走看看