zoukankan      html  css  js  c++  java
  • python之路_基于tcp协议的粘包现象

    1、基于远程执行命令的程序

      需用到subprocess模块

    服务端:

    #1、执行客户端发送的指令
    import socket
    import subprocess
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.bind(('127.0.0.1',8090))
    phone.listen(5)
    while True:
        conn,addr=phone.accept()
        print('IP:%s PORT:%s' %(addr[0],addr[1]))
        while True:
            try:
                cmd=conn.recv(1024)
                if not cmd:break
                #执行命令
                obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE
                                     )
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
                conn.send(stdout+stderr)
            except Exception:
                break
        conn.close()
    phone.close()

    客户端:

    import socket
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8090))
    while True:
        cmd=input('>>>').strip()
        if not cmd:continue
        phone.send(cmd.encode('utf-8'))
        res=phone.recv(1024)
        print(res.decode('gbk'))
    phone.close()

    注意注意注意:

    res=subprocess.Popen(cmd.decode('utf-8'),
    shell=True,
    stderr=subprocess.PIPE,
    stdout=subprocess.PIPE)

    的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码,且只能从管道里读一次结果。

    2、粘包现象

      只有TCP有粘包现象,TCP协议是面向流的协议,这也是容易出现粘包问题的原因。例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束。所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

      此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

    两种情况下会发生粘包。

      发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

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

    解决办法1:

    服务端:

    import socket
    import subprocess
    import struct
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.bind(('127.0.0.1',8090))
    phone.listen(5)
    print('starting...')
    while True:
        conn,addr=phone.accept()
        print('IP:%s,PORT:%s'%(addr[0],addr[1]))
        while True:
            try:
                cmd=conn.recv(1024)
                #执行命令cmd
                obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
                #发送真实数据的描述信息:长度
                header=struct.pack('i',len(stdout)+len(stderr))  #i模式为针对整数型数据,下输出的结果为4
                conn.send(header)
               # 发送真实数据
                conn.send(stdout)
                conn.send(stderr)
            except Exception:
                break
        conn.close()
    phone.close()

    客户端:

    import socket
    import struct
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8090))
    while True:
        cmd=input('>>>').strip()
        phone.send(cmd.encode('utf-8'))
        header=phone.recv(4) #指定接收4个字节
        total_size=struct.unpack('i',header)[0]#对接收的4个字节数据进行解包得到待接收数据大小的元组:(数据大小,)
        #循环接收数据
        total_data=b''
        recv_size=0
        while recv_size<total_size:
            recv_data=phone.recv(1024)
            total_data+=recv_data
            recv_size+=len(recv_data)
        print(total_data.decode('gbk'))
    phone.close()

      由于模块struct的pack方法中使用的i类型存在整数无限大时会出现报错的弊端,故提出如下解决方案:

    解决办法2:

    服务端:

    import socket
    import subprocess
    import struct
    import json
    
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.bind(('127.0.0.1',8090))
    phone.listen(5)
    print('starting...')
    while True:
        conn,addr=phone.accept()
        print('IP:%s,PORT:%s'%(addr[0],addr[1]))
        while True:
            try:
                cmd=conn.recv(1024)
                if not cmd:break
                obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
                #制作报头
                header_dic={'filename': 'a.txt',
                              'total_size': len(stdout)+len(stderr),
                              'md5': 'asdfa123xvc123'}
                header_json=json.dumps(header_dic)
                header_bytes=header_json.encode('utf-8')
                #发送报头长度
                conn.send(struct.pack('i',len(header_bytes)))
                #发送报头
                conn.send(header_bytes)
                #发送数据
                conn.send(stdout)
                conn.send(stderr)
            except Exception:
                break
        conn.close()
    phone.close()

    客户端:

    import socket
    import struct
    import json
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8090))
    while True:
        cmd=input('>>>').strip()
        if not cmd:continue
        phone.send(cmd.encode('utf-8'))
        # 接收报头内容长度
        obj=phone.recv(4)
        header_size=struct.unpack('i',obj)[0]
        #接收报头字典
        header_bytes=phone.recv(header_size)
        header_json=header_bytes.decode('utf-8')
        header_dic=json.loads(header_json)
        total_size=header_dic['total_size']
    
        total_data=b''
        recv_size=0
        while recv_size<total_size:
            recv_data=phone.recv(1024)
            total_data+=recv_data
            recv_size+=len(recv_data)
        print(total_data.decode('gbk'))
    phone.close()
  • 相关阅读:
    js高级程序设计 笔记 --- 引用类型
    es6 简单封装一个 省市县三级下拉框
    js中元素、触点等各种距离的总结
    css实现视觉差的滚动
    js的节流和防抖
    js关于原型,原型链的面试题
    深入理解promise
    vue 同一个组件的跳转, 返回时保留原来的下拉位置
    es6 封装一个登录注册的验证滑块
    洛谷P3203 [HNOI2010]弹飞绵羊(lct)
  • 原文地址:https://www.cnblogs.com/seven-007/p/7598357.html
Copyright © 2011-2022 走看看