zoukankan      html  css  js  c++  java
  • 解决粘包现象

    1. recv 工作原理

    1.能够接收来自socket缓冲区的字节数据;
    2.当缓冲区没有数据可以读取时,recv会一直处于阻塞状态,知道缓冲区至少有一个字节数据可取,或者客户端关闭;
    3.关闭远程端并读取所有数据后,再recv会返回字符串。
    

    2.升级版解决粘包问题

    应用层自定义协议FTP(文件传输协议).

    low版:
    	只能是有限的数据,将总数据长度固定成4个字节作为报头,再recv。当文件数据特别大时,会报错。
        
    升级版优点:
    	1. 自定制报头。 如:dic = {'filename':xxx, 'md5':f454d5asfa5, 'total_size':1024*1024*1024}
        2. 解决文件数据过大问题。
        
    	把报头做成字典,字典里包含将要发送的真实数据的描述信息(大小啊之类的),然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。
    我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的。
    
    	思路:
        服务端:
        1. 自定义报头,字典形式,里面存放文件信息等;
        2. 然后将报头字典 ——> json类型字符串 ——> 编码成 bytes类型数据;
        3. 将报头bytes类型的长度(len())——>固定成4个长度的字节,然后再send();
        4. 将报头bytes类型的数据 send();
        5. 如果数据量特别大时,循环读取、发送字节。
        
        客户端:
        1. 先收报头固定长度,再struct,得到报头数据的长度;
        2. 根据长度接收报头字节数据,再解码,反序列化成字典(json);
        3. 从字典获取文件、数据的描述信息(如文件名、大小等),再获取真实的数据。
        
    
    # 服务端
    import socket
    import subprocess
    import struct
    import json
    phone = socket.socket()
    
    phone.bind(('127.0.0.1',8888))
    phone.listen(5)
    
    while 1:
        conn, addr = phone.accept()
        print(f'与客户端{addr}连接')
    
        while 1:
            try:
                from_client_data = conn.recv(1024)
                if from_client_data.upper() == b'Q':
                    print('客户端已退出!')
                    break
                obj = subprocess.Popen(from_client_data.decode("utf-8"),
                                       shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE,
                                       )
                total_data = obj.stdout.read() + obj.stderr.read()
                print(len(total_data))
    
                head_dict = {'filename': 'client',
                             'md5': 1324654645456,
                             'total_size': len(total_data)
                             }    # 自定义报头,字典类型
                head_str = json.dumps(head_dict)  # 报头转换成json的字符串类型
    
                head_bytes = head_str.encode('utf-8')  # 报头 转换成 bytes 类型
    
                head_len_bytes = struct.pack('i', len(head_bytes))  # 固定报头 字节数
    
                conn.send(head_len_bytes)   # 发送报头的 固定字节
    
                conn.send(head_bytes)       # 发送 报头数据
    
    
                conn.send(total_data)       # 发送 总数据, 可循环发送文件的数据
    
            except ConnectionResetError:
                print('与客户端连接中断!')
                break
        conn.close()
    phone.close()
    
    # 客户端
    
    import socket
    import struct
    import json
    
    phone = socket.socket()
    
    phone.connect(('127.0.0.1', 8888))
    
    while 1:
    
        to_server_data = input('>>>').strip().encode('utf-8')
        if not to_server_data:
            print('内容不能为空!')
            continue
        phone.send(to_server_data)
        if to_server_data.upper() == b'Q':
            break
    
        head_len_bytes = phone.recv(4)  # 接收报头固定字节长度
        head_int = struct.unpack('i', head_len_bytes)[0]  # 报头长度反解成 int 长度
        # print(head_int)
    
        head_bytes = phone.recv(head_int)  # 接收int长度的 报头数据
    
        head_dict = json.loads(head_bytes.decode('utf-8'))    
        # 将bytes类型的报头 转换成原类型(字典)
    
        # print(head_dict['total_size'])
    
        data = b''
        while len(data) < head_dict['total_size']:  # 小于文件的大小,循环
            data = data + phone.recv(1024)
    
        print(len(data))
        print(f"{data.decode('gbk')}")
    
    phone.close()
    

    3. 基于UDP协议的socket通信

    1. 基于UDP协议的socket通信无需建立管道,小开启服务端或者客户端都可以;
    2. 基于UDP协议的socket通信,接收、发送一个消息都是无连接的;
    3. 只有拿到对方IP地址和端口就可以通信发消息,按照顺序接收服务端消息。
    
    # 服务端
    import socket
    
    server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server.bind(('127.0.0.1', 9000))
    
    while 1:
        client_data = server.recvfrom(1024)
        # print(client_data)
        data = client_data[0].decode('utf-8')
        print(f'来自客户端{client_data[1]}的消息:{data}')
    
        to_data = input('>>>').encode('utf-8')
        server.sendto(to_data, client_data[1])
    
    # 客户端
    import socket
    
    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 基于网络的UDP协议的socket
    
    while 1:
        to_server_data = input('>>>:').strip()
        client.sendto(to_server_data.encode('utf-8'), ('127.0.0.1', 9000))
    
        data, addr = client.recvfrom(1024)
        print(f'来自服务端{addr}的消息:{data.decode("utf-8")}')
    
  • 相关阅读:
    linux 学习随笔-shell基础知识
    linux 学习随笔-压缩和解压缩
    解析xml的4种方法详解
    集合工具类
    Map概述
    List集合概述
    Java集合框架
    Spring JdbcTemplate详解
    关于c3p0数据库连接池的简单使用
    Java通过JDBC封装通用DAO层
  • 原文地址:https://www.cnblogs.com/liwenhu/p/11401630.html
Copyright © 2011-2022 走看看