• TCP协议,传输数据:报头 + 数据,循环接收,解决粘包问题


    TCP协议是可靠协议,流式协议。
    所以,一次接收不完全的数据会留在缓存里继续等待接收,而且,流式协议不知道何时数据传输完成。
    这就导致了粘包问题。

    利用 协议 ,规定 报头 ,从 报头 得到 数据的总大小,然后,循环取值,直到接收到 总大小的数据,结束 循环,进行下一次的发送。

    这样就能够知道,一段完整的数据,的开始与结束,更不会粘包,而影响下一次的发送结果。

    报头:数据的总大小
    数据:发送的数据

    协议:要有固定的长度
    这里将数据长度,用
    struct 模块,进行处理,把 int 转为 bytes ,可以转为固定长度

    struct:
    将数字转为固定长度的bytes类型
    stuct.pack('i',111)
    i 模式:将数字转为整型的bytes类型 ,即四个字节长度
    stuct.unpack('i',x)
    得到一个元组,第一个元素,为解压出来的值
    
    接收端先接收,比如,4个字节。
    
    用json,将头部序列化,别的应用也能反序列化得到头部信息(比如,字典 )
    

    以远程执行命令socket为例。

    服务端:

    from socket import *
    import subprocess
    import struct
    
    
    phone=socket(AF_INET,SOCK_STREAM) # 流式协议=》tcp协议
    phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
    phone.bind(('127.0.0.1',8083)) # 0-65535, 1024以前的都被系统保留使用
    phone.listen(5) # 5指的是半连接池的大小
    
    #  服务端应该做两件事
    # 第一件事:循环地从板连接池中取出链接请求与其建立双向链接,拿到链接对象
    while True:
        conn,client_addr=phone.accept()
        # 第二件事:拿到链接对象,与其进行通信循环
        while True:
            try:
                data=conn.recv(1024) # 最大接收的数据量为1024Bytes,收到的是bytes类型
                if len(data) == 0:
                    # 在unix系统洗,一旦data收到的是空
                    # 意味着是一种异常的行为:客户度非法断开了链接
                    break
                result = subprocess.Popen(data.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                out = result.stdout.read()
                err = result.stderr.read()
                conn.send( struct.pack('i',len(out) + len(err)) )
                conn.send(out)
                conn.send(err)
                print("客户端发来的消息:",data.decode('utf-8'))
            except Exception:
                # 针对windows系统
                break
    
        # 6、关闭电话连接conn(必选的回收资源的操作)
        conn.close()
    
        # 7、关机(可选操作)     要实现不断地从半连接池中取出连接请求,服务器不能关闭
        # phone.close()
    

    客户端:

    import socket
    import struct
    
    
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议=》tcp协议
    phone.connect(('127.0.0.1',8083))
    while True:
        msg=input("命令>>>: ").strip() #msg=''
        if len(msg) == 0:continue
        phone.send(msg.encode('utf-8'))
        top = phone.recv(4)
        top_len = struct.unpack('i',top)
        size = 0
        while size < top[0]:
            data=phone.recv(1024)
            print(data.decode('gbk'),end='')
            size += len(data)
    
    #4、关闭连接(必选的回收资源的操作)
    phone.close()
    

    注意:

    subprocess模块中,Popen这个执行命令的结果(stdout,stderr等)只能读一次,read()第一次有结果,第二次就没了
    recv 的 nagle算法:将两个 时间间隔短 , 数据量小 的,结合为一个数据块。(流式协议)
    第一次的send,一般是第一次recv的结果,第二次send,一般是第二次recv的结果。如果三次send,只有两次recv,那第三次的就会存放在缓存中,等下一次recv。
    通常的,recv要不就是立马的send,要不就是上次send,剩余在缓存里的数据,它们根据nagle算法,来形成块,即每次recv得到的结果,数据块。
    下面三张图,自己体会。。。
    

  • 相关阅读:
    BZOJ3575 [Hnoi2014]道路堵塞
    BZOJ4456/UOJ184 [Zjoi2016]旅行者
    BZOJ4455/UOJ185 [Zjoi2016]小星星
    BZOJ1036 [ZJOI2008]树的统计Count
    BZOJ2594 [Wc2006]水管局长数据加强版
    BZOJ3669/UOJ3 魔法森林(LCT)
    BZOJ1012:[JSOI2008]最大数
    洛谷【P1175】表达式的转换
    HDU4699:Editor
    BZOJ3039:玉蟾宫
  • 原文地址:https://www.cnblogs.com/pythonwl/p/12746099.html
Copyright © 2020-2023  润新知