• 黏包-黏包的成因、解决方式及struct模块初识、文件的上传和下载


    黏包:


    同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。

    只有TCP协议中才会产生黏包,UDP协议中不会有黏包(udp协议中数据会直接丢失,俗称丢包)

    #面试
    #首先只有在TCP协议中才有黏包现象,是因为TCP协议是面向流的协议,
    #在发送的数据传输的过程中还有缓存机制来避免数据丢失
    #因此在连续发送小数据的时候,以及接收大小不符的时候,都容易产生黏包现象
    #本质是不知道客户端发送的数据长度
    面试中解释黏包
    #连续send两个小数据
    #两个recv,第一个recv特别小
    
    #面试
    #首先只有在TCP协议中才有黏包现象,是因为TCP协议是面向流的协议,
    #在发送的数据传输的过程中还有缓存机制来避免数据丢失
    #因此在连续发送小数据的时候,以及接收大小不符的时候,都容易产生黏包现象
    #本质是不知道客户端发送的数据长度
    
    #服务端发送数据,经过系统内存,客户端接收,在TCP协议中,如果服务端发送数据过大,或者客户端接收数据过少,超过的数据就会滞纳在内存中
    #从而产生了黏包。但是在UDP过程中则直接丢包了
    #本质问题,不知道客户端发送的数据长度
    
    #在TCP中  如果发送端发送了3次,3次总数据大小为10,接收端如果只接收一次,接收大小>10,那么这3次会合起来一起被接收
    #以为TCP中的优化算法,当几次数据又小又连续,会合并发送(需要又小又连续)
    #因为一次性接收,只需要经历一次网络延时,提高接收效率,
    #并且只要接收方足够大,它会一次性把这三次数据都给合并接收,即使此处有3次接收
    #比如此处 ret1和ret2接收都为空,连续的小数据被ret接收了
    # ret = conn.recv(1024)
    # print(ret)
    # ret1 = conn.recv(1024) #此时ret1和ret2接收到的空,是因为数据都被ret接收了,本来是阻塞的,等待消息传入。
    # ret2 = conn.recv(1024) #但是此时客户端close了,会默认发送空消息过来(根据windows版本不同,也可能直接报错),刚好被ret1和ret2接收了。
    # print(ret1,ret2)
    
    #多个send小的数据连在一起,会发生黏包现象,是TCP内部优化算法造成的
    #注意这几个send必须又短又连续且发送间隔非常短暂(0.01秒都不行)
    黏包问题总结
    #UDP不会黏包,但是udp会丢包
    #tcp会黏包,但是不会丢包
    
    # 黏包成因:
    # TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
    # 收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,
    # 使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
    # 这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
    
    # 当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
    # MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。
    # 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,
    # 这样会产生很多数据包碎片,增加丢包率,降低网络速度。
    
    # 1.是因为tcp的拆包机制,使得消息没有边界
    # 2.当发送端缓冲区的长度大于网卡的MTU时,产生了数据包碎片
    黏包成因-蛮看看就好

    黏包的发现:


    socket模块中,TCP协议的黏包问题发现:(用到了subprocess模块)

    import socket
    import subprocess
    ip_port = ('127.0.0.1',8898)
    buffer_size = 10240
    
    sk = socket.socket()
    sk.bind(ip_port)
    sk.listen()
    
    conn,addr = sk.accept()
    while True:
        cmd = input('>>>>> ')
        if cmd == 'q':
            break
        conn.send(cmd.encode('utf-8'))
        ret = conn.recv(buffer_size).decode('utf-8')
        # ret2 = conn.recv(buffer_size).decode('utf-8')
        print(ret)
        # print(ret2)
    
    conn.close()
    sk.close()
    serve端-黏包
    import socket
    import subprocess
    sk = socket.socket()
    sk.connect(('127.0.0.1',8898))
    
    while True:
        cmd = sk.recv(1024).decode('gbk')
        ret = subprocess.Popen(cmd,shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE
                               )
        std_out = 'stdout: '+(ret.stdout.read()).decode('gbk')
        std_err = 'stderr: '+(ret.stderr.read()).decode('gbk')
        print(std_out)
        print(std_err)
        sk.send(std_out.encode('utf-8'))
        sk.send(std_err.encode('utf-8'))
    sk.close()
    client端-黏包

    黏包自己的小分析:

    #服务端发送数据,经过系统内存,客户端接收,在TCP协议中,如果服务端发送数据过大,或者客户端接收数据过少,超过的数据就会滞纳在内存中(内核态用户态)
    #从而产生了黏包。但是在UDP过程中则直接丢包了
    #本质问题,不知道客户端发送的数据长度
    
    
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8898))
    sk.listen()
    
    conn,addr = sk.accept()
    
    #在TCP中  如果发送端发送了3次,3次总数据大小为10,接收端如果只接收一次,接收大小>10,那么这3次会合起来一起被接收
    #以为TCP中的优化算法,当几次数据又小又连续,会合并发送(需要又小又连续)
    #因为一次性接收,只需要经历一次网络延时,提高接收效率,
    #并且只要接收方足够大,它会一次性把这三次数据都给合并接收,即使此处有3次接收
    #比如此处 ret1和ret2接收都为空,连续的小数据被ret接收了
    ret = conn.recv(1024)
    print(ret)
    ret1 = conn.recv(1024) #此时ret1和ret2接收到的空,是因为数据都被ret接收了,本来是阻塞的,等待消息传入。
    ret2 = conn.recv(1024) #但是此时客户端close了,会默认发送空消息过来(根据windows版本不同,也可能直接报错),刚好被ret1和ret2接收了。
    print(ret1,ret2)
    
    conn.close()
    sk.close()
    
    #多个send小的数据连在一起,会发生黏包现象,是TCP内部优化算法造成的
    #注意这几个send必须又短又连续且发送间隔非常短暂(0.01秒都不行)
    server-黏包问题分析
    import socket,time
    sk = socket.socket()
    sk.connect(('127.0.0.1',8898))
    
    sk.send(b'hello')
    time.sleep(0.01) #此时 hello会单独发送,下面两句才一起发送
    sk.send(b'he22')
    sk.send(b'33llo')
    
    #多个send小的数据连在一起,会发生黏包现象,是TCP内部优化算法造成的
    #注意这几个send必须又短又连续且发送间隔非常短暂(0.01秒都不行)
    
    # import time
    # time.sleep(5)
    sk.close()
    client-黏包问题分析

    黏包的解决


    有两种方式:

    1.比较low的方法:及优劣点分析

    import socket
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',8898))
    sk.listen()
    conn,addr = sk.accept()
    
    while True:
        cmd = input('>>>>> ')
        if cmd == 'q':
            break
        conn.send(cmd.encode('gbk'))
        len_num = conn.recv(1024).decode('utf-8')
        conn.send(b'ok')
        len_num = int(len_num)
        ret = conn.recv(len_num).decode('gbk')
        print(ret)
    
    
    conn.close()
    sk.close()
    
    #好处:确定了我到底要接收多大的数据
        #要在文件中配置了一个配置项:就是每一次recv的大小  buffer = 4096
        #当我们要发送大数据的时候,要明确的告诉接收方要发送多大的数据,以便接收方能够准确的接收到所有数据
        #多用在文件传输的过程中
            #大文件的传输 一定是按照字节读 每一次读固定的字节
            #传输的过程中 发送端一边读一边传,接收端一边收一边写
            #send这个大文件之前,35672字节 send(4096)-....-send(4096)---->0
            #resv这个大文件之前,35672字节 send(4096)-....-send(4096)---->0
    
    #不好的地方:
        #多了一次交互
        #send sendto 在超过一定范围的时候,都会报错
        #程序的内存管理
    server端-方法的好处和坏处
    import socket
    import subprocess
    
    sk = socket.socket()
    sk.connect(('127.0.0.1',8898))
    
    while True:
        cmd = sk.recv(1024).decode('gbk')
        if cmd == 'q':
            break
        ret = subprocess.Popen(cmd,shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)
        std_out = ret.stdout.read()  #注意read本身就是bytes类型。
        std_err = ret.stderr.read()  #此处read完后,类似从list中pop,管道中的值就取出来没了,类似队列
        len_num = str(len(std_out) + len(std_err))
        sk.send(len_num.encode('utf-8'))
        sk.recv(1024)
    
        sk.send(std_out)
        sk.send(std_err)
    sk.close()
    client端

    2.用struct模块解决黏包问题,及struct模块初识

    import socket
    import subprocess
    import struct
    sk = socket.socket()
    sk.bind(('127.0.0.1',8898))
    sk.listen()
    conn,addr = sk.accept()
    
    while True:
        cmd = input('>>>>> ')
        if cmd == 'q':
            break
        conn.send(cmd.encode('gbk'))
        len_byte = conn.recv(4)         #通过struct模块,确定知道传过来的int为4个字节,这里只收取4个字节,保证不黏包
        len_num = struct.unpack('i',len_byte)[0]  #获取到传过来的数字,也就是接下来要接收数据的大小
        ret = conn.recv(len_num).decode('gbk')    #通过len,知道接下来要接收多大的数据,从而不管发送端发了几次,这边可以一次性接收
        print(ret)
    
    
    conn.close()
    sk.close()
    server端-struct
    import socket
    import subprocess
    import struct
    sk = socket.socket()
    sk.connect(('127.0.0.1',8898))
    
    while True:
        cmd = sk.recv(1024).decode('gbk')
        if cmd == 'q':
            break
        ret = subprocess.Popen(cmd,shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)
        std_out = ret.stdout.read()  #注意read本身就是bytes类型。
        std_err = ret.stderr.read()  #此处read完后,类似从list中pop,管道中的值就取出来没了,类似队列
        len_num = len(std_out) + len(std_err)
        len_byt = struct.pack('i',len_num)
        sk.send(len_byt)
        sk.send(std_out)
        sk.send(std_err)
    sk.close()
    client端-struct
    import struct
    #模块可以把一个数字 转换为 一个bytes类型 (二进制)  然后再转回数字类型
    #以int为例  int转换后的bytes类型长度为 4字节,以元组的方式返回 (int,)
    #除了int 还有 float double  long  等等
    #其中,int将在以后很长的时间里都只使用int
    
    num_byte = struct.pack('i',4096)
    print(num_byte)      #b'x00x10x00x00'
    print(len(num_byte)) #长度为4
    
    num_tup = struct.unpack('i',num_byte)
    print(num_tup)          #(4096,)
    print(type(num_tup[0])) #<class 'int'>
    
    #通过struct模块,我们就将int转换为长度为4的bytes,就可以通过先recv(4),获取到一个int类型
    # 这个int类型表示接下来要接收的数据大小
    #和黏包问题解决中比较low的方法比较,省去了一个交互步骤,不用我收到len大小后,再send(b'ok')来避免 len 被黏包
    struct模块初识

    练习:


    大文件的上传和下载

    import os
    import json
    import socket
    import struct
    bufer = 1024
    ip_port = ('127.0.0.1',8898)
    sk = socket.socket()
    sk.bind(ip_port)
    sk.listen()
    conn,addr = sk.accept()
    
    len_bytes = conn.recv(4)   #!!!!!这里怎么可以出错,struct传过来的int 就是4字节
    len_head = struct.unpack('i',len_bytes)[0]
    head_json = conn.recv(len_head).decode('utf-8')
    head = json.loads(head_json)
    filesize = head['filesize']
    with open(head['file_name'],'wb') as f:
        while filesize:
            print(filesize)
            if filesize >= bufer:
                file_write = conn.recv(bufer)
                f.write(file_write)
                filesize -= bufer
            else:
                file_write = conn.recv(filesize)
                f.write(file_write)
                break
    conn.close()
    sk.close()
    server
    import os
    import json
    import socket
    import struct
    bufer = 1024
    ip_port = ('127.0.0.1',8898)
    sk = socket.socket()
    sk.connect(ip_port)
    
    head = {'file_path':r'D:python-全栈九期day33',
            'file_name':'04 python fullstack s9day33 ftp作业分析.mp4',
            'filesize':None}
    file_path = os.path.join(head['file_path'],head['file_name']) #join的用法写错了 !!!
    filesize = os.path.getsize(file_path)
    head['filesize'] = filesize
    head_json = json.dumps(head)
    head_bytes = head_json.encode('utf-8')
    
    #开始用struct转换报头长度
    len_head = len(head_bytes)
    head_pack = struct.pack('i',len_head)
    sk.send(head_pack)
    sk.send(head_bytes)
    with open(file_path,'rb') as f:  #!!!这里rb写成了wb
        while filesize:
            print(filesize)
            if filesize >= bufer:
                content = f.read(bufer)
                sk.send(content)
                filesize -= bufer
            else:
                content = f.read(filesize)
                sk.send(content)
                break
    sk.close()
    client
  • 相关阅读:
    apache性能测试工具
    redis和memcacahe、mongoDB的区别
    redis 安装
    redis介绍
    svn基本命令
    变量
    redis持久化有几种.如何配置
    Sundy_Android开发深入浅出和高级开发视频教程
    VC++ MFC类库基础(55讲全)
    从C++起步到MFC实战VC++软件工程师高端培训 视频保存在 播音员的网盘中
  • 原文地址:https://www.cnblogs.com/gkx0731/p/9727223.html
Copyright © 2020-2023  润新知