zoukankan      html  css  js  c++  java
  • python学习第33天网络编程part3通信循环、bug处理、链接循环、远程控制命令、粘包问题处理

    之前简单介绍了tcp和udp的服务端和客户端,但一个完整的服务端必须至少满足三个功能

    (1)绑定一个固定的ip和port

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

    (3)能够支持并发

    一、通信循环

    对于客户端与服务端,不单单只能交流一次,正常需要交流多次,这时候需要支持通信循环,用while循环实现多次交流

    服务端:

    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()

    客户端:

    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()

    二、bug处理

    (1)起因:当客户端非正常的断开,服务端就会报错,可预知但无法准确知道。

    过程分析:客户端输入了空,服务端不会收到空数据;如果服务端收到了空数据,肯定是客户端单方面的把链接异常中断掉,而在在windows系统上服务端就会抛出异常,在Linux系统上服务端recv一直收空,无法预知异常发生的条件

    解决方法:异常处理    try...except

    服务端

    from socket import *
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1', 8080))
    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()

    (2)起因:当用户端输入空时候,用户端堵塞住

    过程分析:当用户端输入了空数据,用户端的操作系统收不到数据,收不到数据不会传到用户端的网卡,也就不能传输数据,服务端收不到数据,所以

    服务端也不会回发数据,用户端也就收不到数据最终堵塞

    解决方法:添加判断,不让用户端输入空

    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()

    三、链接循环

    单单一个服务端和客户端交流是不够的,需要多个客户端可以与链接循环

    服务端

    from socket import *
    
    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:
                data = conn.recv(1024)
                if len(data) == 0: break  # 针对linux系统
                print('-->收到客户端的消息: ', data)
                conn.send(data.upper())
            except ConnectionResetError:
                break
    
        conn.close()
    
    server.close()

    客户端

    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()

     四、模拟ssh实现远程执行命令

    为了执行系统命令,服务端需要导入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()

     客户端:

    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()

    五、粘包问题

    1、粘包问题的起因:tcp协议流式传输数据的方式 导致的,必须要全部收完

    假设客户端、服务端接受的数据为1024个字节

    当发送的数据量大于接受量1024的时候,就会出现每次接受只有1024个字节,剩余的字节等下次命令/输入的时候再输入过来,导致

    命令错乱

    2、解决思路:

    (1)方案一:可以提高接受数据的上限,但是提高也有上限的,当设置的上限过高或者超过内存大小的时候会报错,

    而且往往传输的文件大于内存,这个方法不适用

    (2)方案二:自定义报头 ,循环多收几次收干净,一次性打印出来

    步骤一:设计一个固定长度的报头数据,报头含有数据流的长度,长度是bytes才能传输,需要导入

    struct模块,把整型转化为bytes,底层原理就是报头与真正的数据连在一起

    ps;补充struct模块的使用

    import struct
    
    # obj1=struct.pack('i',13321111111)
    # print(obj1,len(obj1))          将整型转化为bytes
    
    # res1=struct.unpack('i',obj1)          将bytes反解化成整型
    # print(res1[0])
    
    obj1=struct.pack('q',1332111111111111111111111)
    print(obj1,len(obj1))

    服务端发给客户端:

    1先制作固定长度的报头
    2再发送报头
    3最好发送真实的数据
    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=struct.pack('i',len(stdout) + len(stderr))
                # 2. 再发送报头
                conn.send(header)
                # 3. 最后发送真实的数据
                conn.send(stdout)
                conn.send(stderr)
            except ConnectionResetError:
                break
    
        conn.close()
    
    server.close()

    步骤二:

    客户端接受数据

    1、先收报头,从报头里解出数据的长度

    2、接受真正的数据

    3、每次以1024个数据回收数据,直到收到的数据长度大于含报头的所有数据流的长度,就不收了,然后打印所有收到的数据

    from socket import *
    import struct
    
    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. 先收报头,从报头里解出数据的长度
        header=client.recv(4)
        total_size=struct.unpack('i',header)[0]
        #2. 接收真正的数据
        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()

    (3)方案三(完整版):方案二还有个问题没有解决,那就是如果当报头中的文件长度很大,而struct只能把整型转化为4、8字节,当发送的数据很大的时候也会超出struct的转化长度,struct也无法转化数据的长度

    解决思路:1、先制作报头,把报头做成一个字典包含比如文件名、md5值、数据长度等等

    序列化字典得到字典的bytes,这时候的bytes长度就很小

    这时候struct就可以又把字典的bytese的长度转化为固定4个bytes

    2、先发送4个bytes(包含报头的长度)
    3、在发送报头

    4、最好发送真实的数据

    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()

    客户端接受数据

    1先收4bytes,解出报头的长度

    2再接受报头,拿到字典

    3接受真正的数据

    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()
  • 相关阅读:
    服务注册中心之Eureka使用
    微服务之服务注册中心
    Idea热部署功能
    微服务Cloud整体聚合工程创建过程
    微服务架构理论&SpringCloud
    关于母函数
    HDU 1028(母函数)整数划分
    1021 FIBERNACI
    1019
    1014 巧妙的gcd 生成元
  • 原文地址:https://www.cnblogs.com/ye-hui/p/9911736.html
Copyright © 2011-2022 走看看