zoukankan      html  css  js  c++  java
  • TCP粘包以及解决方案

    TCP协议粘包现象的说明:

    1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

    粘包会发生什么?

    这时候,接受不知道数据的限界,就没有办法正确的解析对方传输过来的限界。就才去了类似通信协议的解决方案,处理粘包问题。

    简单解决问题的方法。根据当前需要发送的数据的大小传输数据的二进制长度先发送给客户端,在根据传输数据的长度来获取的真实的数据。

    实现如下服务端

    import socket,subprocess,struct
    server = socket.socket()
    server.bind(("127.0.0.1",54321))
    server.listen(5)
    
    while True:
        client, addr = server.accept()
        while True:
            try:
    
                cmd = client.recv(1024).decode("utf-8")#接受命令
                if not cmd:
                    print("client closed")
                    client.close()
                    break
                p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                res = p.stdout.read()+p.stderr.read()#获得返回的数据
                res_len =len(res)#整形形式的需要转换为固定长度的二进制形式。使用struct模块
                bytes_len = struct.pack("i",res_len)
                client.send(bytes_len)
                client.send(res)
            except ConnectionResetError:
                print("客户端错误退出")
                client.close()
                break
    server.close()

    客户端

    import socket,struct
    
    client =socket.socket()
    client.connect(("127.0.0.1",54321))
    
    while True:
        cmd = input(">>>:")
        if not cmd:continue
        client.send(cmd.encode("utf-8"))
        #获取数据的长度的二进制
        bytes_len = client.recv(1024)
        #解压成为整形
        res_len = struct.unpack("i",bytes_len)[0]
        #接受数据的方式
        final_data=b""
        #当前接受数据的长度计数器
        data_len = 0
        while data_len<res_len:
            res = client.recv(1024)
            final_data += res
            data_len += len(res)
        print(final_data.decode("gbk"))
    
    client.close()

    其实我们还以定制更加复杂的数据头

    可以字典形式表现,实现传输更加复杂文件,甚至可以进行校验

    服务端

    import socket,struct,json
    
    server = socket.socket()
    server.bind(("127.0.0.1",3333))
    server.listen(5)
    while True:
        client,addr = server.accept()
        while True:
            try:
                ml = client.recv(1024).decode("utf-8")#模拟客户端请求报告
                if not ml:
                    client.close()
                    break
                if ml != "1":continue
                #开始资质表头
                filename = "总结"
                #计算文件bytes长度
                file_len = 0
                f = open(r"F:Python_exeday34上午总结","rb")
                for line in f:
                    line_len = len(line)
                    file_len += line_len
                f.close()
                file_dict = {"filename":filename,"length":file_len}
                #将字典装换为json格式
                json_dict = json.dumps(file_dict)
                #再json字符串转为bytes
                byte_dict = json_dict.encode("utf-8")
                #计算字典二进制的长度
                head_len = len(byte_dict)
                #将长度打包成一个数据头
                head = struct.pack("i", head_len)
                client.send(head)
                client.send(byte_dict)
                #标记内容完成
                #传输正式内容
                with open(r"F:Python_exeday34上午总结","rb") as f:
                    for line in f:
                        client.send(line)
    
    
            except ConnectionResetError:
                print("连接错误连接")
                client.close()
                break

    客户端

    import socket,struct模块,json
    
    client = socket.socket()
    client.connect(("127.0.0.1",3333))
    
    while True:
        ml = input(">>>:")
        if not ml:continue
        client.send(ml.encode("utf-8"))
        #获取字典的二进制
        bytes_len = client.recv(4)
        #还原
        head_len =struct模块.unpack('i', bytes_len)[0]
        #获取数据报头
        bytes_dict = client.recv(head_len)
        #将bytes类型转化为json格式
        json_dict = bytes_dict.decode("utf-8")
        #将字典json格式还原为成python的字典
    
        file_dict=json.loads(json_dict)
        #获取文件的名称
        filename = file_dict["filename"]
        #获取文件的传输长度
        file_len = file_dict["length"]
        #打开文件追加写
        f = open(filename,"ab")
        #当前接受长度
        data_len = 0
    
        while data_len<file_len:
            data = client.recv(1024)
            data_len += len(data)
            f.write(data)
        f.close()
        client.close()
    
    client.close()
    解决粘包的方案  自定义报头
    1.先用报头传输数据的长度
    对于我们远程CMD程序来说 只要先传输长度就能解决粘包的问题
    但是如果做得是一个文件上传下载 除了数据的长度 还需要传输文件的名字 md5等等信息
    又该如何?

    2.自定义复杂报头 完成发送一些额外的信息 例如文件名
    1.将要发送的额外数据打包成一个字典
    2.将字典转为bytes类型
    3.计算字典的bytes长度 并先发送
    4.发送字典数据
    5.发送真实数据
  • 相关阅读:
    一键复制文本框内容代码、
    改掉这些坏习惯,你不再是菜鸟
    使用cookie保存页面登录信息
    二维数组转换成一维数组
    jQuery选择器总结
    cookie 和session 的区别详解
    PHP扫雷(转载)。
    PHP简易计算器方法2
    PHP简易计算器方法1
    业务逻辑的存储过程(添加学生的案例)(自动编号)
  • 原文地址:https://www.cnblogs.com/msj513/p/9911596.html
Copyright © 2011-2022 走看看