• python 中粘包问题(tcp通信下)python中上传下载文件实现方法。


    一花一世界,一叶一菩提。

    粘包现象

    粘包的现象:

    [root@localhost]# netstat -ano
    活动连接
    
      协议  本地地址          外部地址        状态           PID
      TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       468
      TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
      TCP    0.0.0.0:5040           0.0.0.0:0              LISTENING       1988
      TCP    0.0.0.0:5357           0.0.0.0:0              LISTENING       4
      TCP    0.0.0.0:11200          0.0.0.0:0              LISTENING       5420
      TCP    0.0.0.0:16422          0.0.0.0:0              LISTENING       5420
      TCP    0.0.0.0:27036          0.0.0.0:0              LISTENING       7232
      TCP    0.0.0.0:49664          0.0.0.0:0              LISTENING       660
      TCP    0.0.0.0:49665          0.0.0.0:0              LISTENING       1444
      TCP    0.0.0.0:49666          0.0.0.0:0              LISTENING       1744
      TCP    0.0.0.0:49667          0.0.0.0:0              LISTENING       3684
      TCP    0.0.0.0:49671          0.0.0.0:0              LISTENING       736
      TCP    0.0.0.0:49679          0
    [root@localhost]# 
    

    明显可以看出这是一个==不完整的返回!==那么我们如果在输入一次 dir结果会是什么样的呢?

    篇幅问题请自行测试吧。(文章后面可能会给附加文件!)**

    在这里插入图片描述

    明显的一个粘包问题!

    为什么会有粘包问题呢?

    须知:只有TCP有粘包现象,UDP永远不会粘包,为何,且听我娓娓道来

    首先需要掌握一个socket收发消息的原理

    img

    我们都知道我们把TCP协议称之为流水协议,就是把它当做流水一样去看待,一滴滴的水从一头滴落到另外一头。

    我们来举个例子:

    第一次我们客户端传输了700个字节,然后我们服务端开始取,因为我们设置的是1024个字节为收取,第一次我们收取到的是700个字节。

    那么第二次我们客户端又传输了1500个字节,然后我们服务端开始取,因为我们设置的是1024个字节为收取所以这一次我们最多可以收取到的是1024个字节。那么我们还收取第二次吗?(除非是在循环取的,不然我们是不可能继续取的。而且如果是循环是不是就感觉很LOW)

    那么第三次我们客户端又传输了300个字节,然后我们服务端开始取,因为我们设置的是1024个字节为收取这一次我们收取到的是1024个字节。那么为什么是1024个字节,而不是300个字节呢,(那就要说道TCP协议了,TCP协议中它是以个稳定协议,也就是说它永远都会把所有数据都发完,不会留着,也不会随机发多少一说),而我们发过去的数据量有都是存在服务端的缓存中不会丢失的,就像我们去井口去取水,我们不会把最下面的水拿出来把,拿出来的肯定是最上面的。所以就发生了所谓的粘包现象,也就是说,一个没取干净,导致全程乱序的事故

    总结:

    简而言之:所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

    两种情况下会发生粘包。

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

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

    解决粘包问题的方法

    struct模块

    用这个struct模块有什么好处

    为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

    该模块可以把一个类型,如数字,转成固定长度的bytes

    >>> struct.pack('i',1111111111111)
    
    struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
    

    img

    TCP通信下普通报头添加

    对于就以种要求的报头处理方法

    (比如只需要知道后续文件的长度。)

    服务端:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    """
    服务端应该满足的特性:
        1、一直对外提供服务
        2、并发地提供服务
    """
    import socket
    import subprocess
    import struct
    
    # 1、买手机
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM=》TCP协议
    
    # 2、插手机卡
    phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 就是它,在bind前加
    
    phone.bind(("127.0.0.1", 8080))  # 本地回环
    
    # 3、开机
    phone.listen(5)
    print('starting %s:%s' % ("127.0.0.1", 8080))
    
    # 4、等电话链接=>链接循环
    while True:
        conn, client_addr = phone.accept()
        print(client_addr)
        # 5、收/发消息=>通信循环
        while True:
            try:
                cmd = conn.recv(1024)  # 最大接收的字节个数
                if len(cmd) == 0:  # 针对linux系统
                    break
    
                obj = subprocess.Popen(cmd.decode('utf-8'),
                                       shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE
                                       )
    
                stdout = obj.stdout.read()
                stderr = obj.stdout.read()
                total_size = len(stdout) + len(stderr)
                # 先发送数据的长度
                conn.send(struct.pack('i', total_size))
                # 发送真正的数据
                conn.send(stdout)
                conn.send(stderr)
            except Exception:  # 针对windows系统
                break
    
        # 6、关闭
        conn.close()  # 挂电话
    phone.close()  # 关机
    
    

    客户端:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import socket
    import struct
    # 1、买手机
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM=》TCP协议
    # 2、拨电话
    phone.connect(("127.0.0.1", 8080))
    # 3、发/收消息=>通信循环
    while True:
        cmd = input("[root@localhost]# ").strip()
        if len(cmd) == 0:
            continue
        phone.send(cmd.encode('utf-8'))
        # 先收数据的长度
        header = phone.recv(4)
        total_size = struct.unpack('i', header)[0]
        # 收真正的数据
        recv_size = 0
        res = b''
        while recv_size < total_size:
            data = phone.recv(1024)
            res += data
            recv_size += len(data)
        print(res.decode('gbk'))
    # 4、关闭
    phone.close()
    

    结果:

    自行测试(输入cmd 中的方法 例如 dir 等等…)

    TCP通信下复杂报头添加

    对于需要处理多个信息的情况

    思路:

    我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)
    
    发送时:
    
    先发报头长度
    
    再编码报头内容然后发送
    
    最后发真实内容
    
     
    
    接收时:
    
    先手报头长度,用struct取出来
    
    根据取出的长度收取报头内容,然后解码,反序列化
    
    从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
    

    上传下载文件代码:

    服务端:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import os
    import struct
    import json
    import hashlib
    from socket import *
    
    server = socket(AF_INET, SOCK_STREAM)
    # print(server)
    server.bind(('127.0.0.1', 8082))
    server.listen(5)
    while True:
        conn, client_addr = server.accept()
        print(client_addr)
        while True:
            try:
                msg = conn.recv(1024).decode('utf-8')
                cmd,file_path=msg.split()
                m5 = hashlib.md5()
                print(cmd)
                if cmd == "get":
                    with open(r'%s' % file_path, mode='rb') as f:
                        for line in f:
                             m5.update(line)
    
                    md5=m5.hexdigest()
                    # 一、制作报头
                    header_dic={
                        "total_size":os.path.getsize(file_path),
                        "filename":os.path.basename(file_path),
                        "md5":md5}
                    header_json=json.dumps(header_dic)
                    header_json_bytes=header_json.encode('utf-8')
                    # 二、发送数据
                    # 1、先发送报头的长度
                    header_size=len(header_json_bytes)
                    conn.send(struct.pack('i',header_size))
                    # 2、再发送报头
                    conn.send(header_json_bytes)
                    # 3、最后发送真实的数据
                    with open(r'%s' %file_path,mode='rb') as f:
                        for line in f:
                            conn.send(line)
                elif cmd == 'dup':
                    print('2')
                    # 1、先接收报头的长度
                    n = 0
                    header = b''
                    while n < 4:
                        data = conn.recv(1)
                        header += data
                        n += len(data)
                    header_size = struct.unpack('i', header)[0]
                    # 2、再接收报头
                    header_json_bytes = conn.recv(header_size)
                    header_json = header_json_bytes.decode('utf-8')
                    header_dic = json.loads(header_json)
                    # 3、最后接收真实的数据
                    total_size = header_dic['total_size']
                    filename = header_dic['filename']
                    recv_size = 0
                    with open(fr'{file_path}', mode='wb') as f:
                        while recv_size < total_size:
                            data = conn.recv(1024)
                            f.write(data)
                            recv_size += len(data)
            except Exception:
                break
        conn.close()
    
    server.close()
    
    

    客户端:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import struct
    import json
    import hashlib
    import os
    from socket import *
    #客户端上传路径(服务器中所存放的位置)
    
    #dup C:UsersAdministratorDesktop每日笔记day32图片2	img.jpg
    
    #客户端上传路径 (用户所需要上传文件的位置)
    
    #D:图片	img.jpg
    
    
    # 客户端下载路径(服务器中文件所在的位置):
    
    #get D:图片	img.jpg
    
    # 客户端下载路径(用户所需要存放该文件的位置):
    
    #C:UsersAdministratorDesktop每日笔记day32图片
    
    client = socket(AF_INET, SOCK_STREAM)
    # print(client)
    client.connect(('127.0.0.1', 8082))
    
    while True:
        cmd = input("服务器文件路径/或者目标地址: ").strip()  # get 文件路径
        addr=input('存放路径/目标存放文件路径: ').strip()
        if len(cmd) == 0:
            continue
        if len(addr) == 0:
            continue
        cmds, file_path = cmd.split()
        if cmds =='get':
            client.send(cmd.encode('utf-8'))
            # 1、先接收报头的长度
            n = 0
            header = b''
            while n < 4:
                data = client.recv(1)
                header += data
                n += len(data)
            header_size=struct.unpack('i',header)[0]
            # 2、再接收报头
            header_json_bytes=client.recv(header_size)
            header_json=header_json_bytes.decode('utf-8')
            header_dic=json.loads(header_json)
            print(header_dic)
            # 3、最后接收真实的数据
            total_size=header_dic['total_size']
            filename=header_dic['filename']
            recv_size = 0
            with open(fr"{addr}%s" %filename, mode='wb') as f:
                while recv_size < total_size:
                    data = client.recv(1024)
                    f.write(data)
                    recv_size += len(data)
        elif cmds=='dup':
    
            client.send(cmd.encode('utf-8'))
            m5 = hashlib.md5()
            with open(fr'{addr}', mode='rb') as f:
                print('打开文件')
                for line in f:
                    m5.update(line)
            md5 = m5.hexdigest()
            # 一、制作报头
            header_dic = {
                "total_size": os.path.getsize(addr),
                "filename": os.path.basename(addr),
                "md5": md5}
            print(header_dic)
            header_json = json.dumps(header_dic)
            header_json_bytes = header_json.encode('utf-8')
            # 二、发送数据
            # 1、先发送报头的长度
            header_size = len(header_json_bytes)
            client.send(struct.pack('i', header_size))
            # 2、再发送报头
            client.send(header_json_bytes)
            # 3、最后发送真实的数据
            with open(fr'{addr}', mode='rb') as f:
                for line in f:
                    client.send(line)
            print('文件上传成功!')
    client.close()
    

    结果:

    自行测试(输入上述客户端所提示的地址。请看清楚。)

    峰哥详解地址

    努力学习!
  • 相关阅读:
    php程序去除文件 bom头
    类继承接口后,实现接口的方法
    virtual和abstract的使用场景分别是什么?待更新。
    get请求和post请求的总结
    ES6新语法,持续更新
    display:flex中的不懂的问题,待处理
    css相邻兄弟选择器(+),伪类(:hover),伪元素(::after)
    HTML中的input的type类型
    命令行打开程序的集合
    sqlserver 常用的语句
  • 原文地址:https://www.cnblogs.com/Orange-YXH/p/13648099.html
Copyright © 2020-2023  润新知