zoukankan      html  css  js  c++  java
  • tcp粘包

    Socket粘包问题

    参考博客

    https://www.cnblogs.com/nickchen121/p/11031027.html

    首先要注意,只有TCP有粘包问题,UDP没有

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

    UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。

    TCP是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略

    一般有两种形式的粘包问题

    1.发送端需要等缓冲满才发送出去,造成粘包

    2.接收端不及时接收缓冲区的包,造成粘包

    解决粘包问题

    在发送数据的头上,加入数据的相关信息,文件名,文件长度等,接收端接受到数据后先解析数据头,然后根据数据长度读取数据,在已文件名保存指定格式的文件。

    文件上传下载,粘包版

    服务端

    from socket_server import *
    import struct
    import json
    import os
    
    server=socket(AF_INET,SOCK_STREAM)
    
    server.bind(('127.0.0.1',9000))
    
    server.listen(5)
    
    print("等待客户端连接")
    conn,addr=server.accept()
    print(f"{addr}连接成功")
    
    def dow_file(file_name):
    
        file_path=os.path.join("haha",file_name)
    
        if os.path.exists(file_path):
            print(f"发送文件{file_name}")
            with open(f"haha/{file_name}","rb")as fr:
                file_data=fr.read()
    
            #文件字典
            file_dic={}
            file_dic['file_name']=file_name
            file_dic['command'] = "down"
            file_dic["file_len"]=len(file_data)
    
            #字典长度 第一个头
            head1=struct.pack('i',len(bytes(json.dumps(file_dic),encoding="utf8")))
            conn.send(head1)
    
            #文件名和大小 第二个头
            head2=bytes(json.dumps(file_dic),encoding="utf8")
            conn.send(head2)
    
            #文件内容
            conn.send(file_data)
            print("文件发送成功")
        else:
            print(f"{file_name}文件不存在")
            head1 = struct.pack('i',0)
            conn.send(head1)
    def up_file(file_dic):
        # 获取文件名和文件长度
        file_name = file_dic['file_name']
        file_len = file_dic['file_len']
    
        # 文件写入
        print(f"{file_name}正在写入")
        with open(f"haha/{file_name}", "wb")as fw:
            while file_len > 0:
                if file_len > 1024:
                    data = conn.recv(1024)
                    file_len -= 1024
                else:
                    data = conn.recv(file_len)
                    file_len = 0
                fw.write(data)
            print(f"{file_name}写入成功")
    
    if __name__ == '__main__':
        while True:
            # 获取文件字典的长度
            header = conn.recv(4)
            head2_len = struct.unpack('i', header)[0]
    
            # 获取文件字典
            file_dic = conn.recv(head2_len)
            file_dic = json.loads(file_dic.decode('utf8'))
    
            if file_dic["command"]=="down":
                dow_file(file_dic["file_name"])
            if file_dic["command"]=="up":
                up_file(file_dic)
    
    
    
    # conn.close()
    # server.close()
    

    客户端

    import struct
    from socket import socket
    import json
    import os
    
    client=socket()
    client.connect(('127.0.0.1',9000))
    
    def down_file():
        file_name = input("请输入想要下载的文件:")
        data = bytes(file_name, encoding="utf8")
    
        file_dic = {}
        file_dic["command"] = "down"
        file_dic['file_name'] = file_name
    
        # 字典长度 第一个头
        head1 = struct.pack('i', len(bytes(json.dumps(file_dic), encoding="utf8")))
        client.send(head1)
    
        # 文件名和大小 第二个头
        head2 = bytes(json.dumps(file_dic), encoding="utf8")
        client.send(head2)
    
    
        # 字典长度 第一个头
        head1 = struct.pack('i', len(bytes(json.dumps(file_dic), encoding="utf8")))
        client.send(head1)
    
        client.send(head2)
    
        # 获取文件字典的长度
        header = client.recv(4)
        head2_len = struct.unpack('i', header)[0]
        if head2_len == 0:
            print("文件不存在")
            return
    
        # 获取文件字典
        file_dic = client.recv(head2_len)
        file_dic = json.loads(file_dic.decode('utf8'))
    
        # 获取文件名和文件长度
        file_name = file_dic['file_name']
        file_len = file_dic['file_len']
    
        # 文件写入
        print(f"{file_name}正在写入")
        with open(f"zx/{file_name}.py", "wb")as fw:
            while file_len > 0:
                if file_len > 1024:
                    data = client.recv(1024)
                    file_len -= 1024
                else:
                    data = client.recv(file_len)
                    file_len = 0
                fw.write(data)
            print(f"{file_name}写入成功")
    
    def up_file():
        file_name = input("请输入你想要上传的文件名:")
        file_path = os.path.join("zx", file_name)
    
        if os.path.exists(file_path):
            print(f"发送文件{file_name}")
            with open(f"zx/{file_name}", "rb")as fr:
                file_data = fr.read()
    
            file_dic = {}
            file_dic['file_name'] = file_name
            file_dic["command"] = "up"
            file_dic["file_len"] = len(file_data)
    
            # 字典长度 第一个头
            head1 = struct.pack('i', len(bytes(json.dumps(file_dic), encoding="utf8")))
            client.send(head1)
    
            # 文件名和大小 第二个头
            head2 = bytes(json.dumps(file_dic), encoding="utf8")
            client.send(head2)
    
            # 文件内容
            client.send(file_data)
            print("文件发送成功")
        else:
            print(f"{file_name}文件不存在")
            return
    
    fun_dic={
        "0":down_file,
        "1":up_file
    }
    if __name__ == '__main__':
        while True:
            print(
                '''
                0:下载文件
                1:上传文件
                '''
            )
            choice=input("请选择你要的功能:").strip()
            if not fun_dic.get(choice):
                print("没有此功能")
                continue
            fun_dic[choice]()
    
    # client.close()
    
  • 相关阅读:
    DHCP服务器与DHCP中继服务器实验
    DAY1-作业
    logging模块的基本使用
    01_docker镜像命令
    00_docker的基本组成
    21_django配置使用mysql数据库的两种方式
    08_使用python操作mysql
    07_mysql的基本操作
    06_python操作mongodb
    05_MongoDB基本操作
  • 原文地址:https://www.cnblogs.com/zx125/p/11494762.html
Copyright © 2011-2022 走看看