zoukankan      html  css  js  c++  java
  • 粘包及其相关知识

      一, subprocess模块

      可以执行操作系统的命令,即可以返回正确结果,也可以返回错误结果

      1. 语法:

    import subprocess
    r=subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
    #subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)    
    #cmd : 代表系统命令
    #shell=True 代表这条命令是系统命令,告诉操作系统将cmd当成系统命令执行
    #stdout : 是执行完系统命令后,用于保存结果的一个管道(管道的内容只能取一次)
    #stderr : 是执行完系统命令之后,用于保存错误结果的一个管道(只能取一次) 

    print(r.stdout.read().decode('gbk'))

    print(r.stderr.read().decode('gbk'))

      2. 用途举例:

       需求: 客户端发送要执行命令 ; 服务器执行,执行完将结果返回给客户端 ; 客户端拿到结果呈现到

        用户眼前

    客户端
    
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1',7080))
    
    com = input('请输入命令:>>')
    sk.send(com.encode('utf-8'))    #发送给服务器要执行的命令
    resuld = sk.recv(10240).decode('gbk')#win默认gbk编码  此处接收服务器端发送的命令执行结果
    print(resuld)
    
    sk.close()
    
    
    服务器端
    
    import socket
    import subprocess
    sk = socket.socket()
    sk.bind(('127.0.0.1',7080))
    sk.listen()
    
    conn , addr = sk.accept()
    cmd = conn.recv(10240).decode('utf-8')    #接收到客户端要执行的命令
    r = subprocess.Popen(cmd,shell = True,stdout=subprocess.PIPE,
                                      stderr = subprocess.PIPE  )  #调用操作系统执行命令
    
    stdout = r.stdout.read()
    stderr = r.stderr.read()
    if stderr :    #若果命令执行后错误管道不为空,则执行
        conn.send(stderr)
    else:    #若错误管道为空,则执行
        conn.send(stdout)
    
    conn.close()
    sk.close()

    二, 粘包

      只有tcp协议有,udp协议不会发生粘包.

      nagle算法(合包机制) : 把缓存区连续间隔时间较短的数据块打包统一发送,

    服务器端
    
    # import socket
    # sk = socket.socket()
    #
    # sk.bind(('127.0.0.1',8888))
    # sk.listen()
    #
    # conn,addr = sk.accept()
    #
    # conn.send(b'hello')
    # conn.send(b'world')
    #
    # conn.close()
    # sk.close()
    
    
    
    客户端
    # import socket
    # sk = socket.socket()
    #
    # sk.connect_ex(('127.0.0.1',8888))
    #
    # msg1 = sk.recv(1024)
    # print('msg1:',msg1)
    #
    # msg2 = sk.recv(1024)
    #
    # print('msg2:',msg2)
    #
    # sk.close()

    #粘包时结果为 : msg1:b'helloworld'
             msg2:b''

      合包机制:

      拆包机制

      在发送端,因为受到网卡的MTU限制,会将大的超过MTU限制的数据,进行拆分,拆分成多个小的

      数据,进行传输,当传输到目标主机的操作系统层时,会重新将多个小的数据合并成原本的数据

      udp协议不会发生粘包

      udp协议本层对一次收发数据大小的限制是:

          65535 - ip包头(20) - udp包头(8) = 65507

       站在数据链路层,因为网卡的MTU一般被限制在了1500,所以对于数据链路层来说,一次收发数

      据的大小被限制在  1500 - ip包头(20) - udp包头(8) = 1472

      得到结论:

          如果sendto(num)

           num > 65507  报错

           1472 < num < 65507  会在数据链路层拆包,而udp本身就是不可靠协议,所以一旦拆包之

        后,造成的多个小数据包在网络传输中,如果丢任何一个,那么此次数据传输失败

           num < 1472 是比较理想的状态

      大文件传输不粘包(耗时版)

    服务器端
    
    import socket
    import json
    import os
    sk = socket.socket()
    sk.bind(('127.0.0.1',8090))
    sk.listen()
    
    conn ,addr = sk.accept()
    print(conn)
    re = conn.recv(1024).decode('utf-8')
    conn.send(b'ok')
    re = json.loads(re)
    if re['men'] == 'upload':
        filename = '1' + re['filename']
        with open(filename , 'ab') as f:
            while re['filesize'] :
                content = conn.recv(1024)
                f.write(content)
                re['filesize'] = re['filesize'] - len(content)
    
    if re['men'] == 'download': #判断所接收的是不是下载功能
        print(conn)
        dic = {'men': re['men'], 'filename': ' ', 'filesize': ' '}
        # D:py周作业源文件计算器5计算器.py
        filename = os.path.basename(re['file_path'])
        filesize = os.path.getsize(re['file_path'])
        dic['filename'] = filename
        dic['filesize'] = filesize
        str_dic = json.dumps(dic)
        conn.send(str_dic.encode('utf-8'))#第一次发送字典
        conn.recv(1024)#接收客户端发送的b'ok'
        with open(re['file_path'], 'rb') as f:
            while filesize:
                content = f.read(1024)
                conn.send(content)#把要下载的内容发送给客户端
                filesize = filesize - len(content)
        print(dic)
    conn.close()
    sk.close()
    
    
    
    
    客户端
    menu = {'1':'upload','2':'download'}
    for k , v in menu.items():
        print(k,v)
    choose = input('请选择功能:')
    if choose == '1':
        dic = {'men':menu.get(choose),'filename':' ','filesize':' '}
        file_path = input('请输入一个绝对路径:')
        filesize = os.path.getsize (file_path)#获取用户输入的文件路径下文件的大小
        filename = os.path.basename(file_path)#文件名字
        dic['filename'] = filename#把文件名封装到字典
        dic['filesize'] = filesize#把用户输入的文件大小封装到字典
        print(dic)
        str_dic = json.dumps(dic)#字典序列化
        sk.send(str_dic.encode('utf-8'))#把字典发送给服务器
        status = sk.recv(1024) #返回给客户端用于延长时间,防止连续传输粘包
        print(status.decode('utf-8'))
        with open (file_path , 'rb' ) as f:
            while filesize :
                content = f.read(1024)#规定每行读取到的内容1024
                sk.send(content)#阶段发送
                filesize = filesize - len(content)#剩余文件大小减去发送的文件大小
    
    elif choose == '2':
        dic = {'men': menu.get(choose), 'file_path': ' ', 'filesize': ' '}
        dic['file_path'] = input('请输入一个绝对路径:')
        str_dic = json.dumps(dic)
        sk.send(str_dic.encode('utf-8'))    #把封装后的字典发送给服务器
        b_ok = sk.recv(1024) #接收服务器端内容,接收的是 第一次的b'ok'
        dic = sk.recv(1024).decode('utf-8')#接收的是第一次服务器端发送的字典
        sk.send(b'ok')#客户端发送的 b'ok'
        str_di = json.loads(dic)
        with open(str_di['filename']+'1' , 'ab' ) as f :
            while str_di['filesize'] :
                content = sk.recv(1024)
                f.write(content)   #把服务器端读出内容进行下载即是写入文件
                str_di['filesize'] = str_di['filesize'] - len(content)
                print(content)
    
    sk.close()

      struct 模块

       struct.pack(type,num)

        type : 是num的类型 ;  num : 是一个数字

        把一个数字打包成一个四字节的bytes类型的形式

       struct.unpack(type,r)

        功能 : 解包,把上述bytes形式的数据还原成原数字,存放在元组中,原数字在元组中的下标

        为 0.

      不粘包优化版 

    服务端
    import socket
    import json
    import os
    import struct
    sk = socket.socket()
    sk.bind(('127.0.0.1',8090))
    sk.listen()
    conn ,addr = sk.accept()
    print(conn)
    
    len_dic = conn.recv(4)#接收字典的大小,四位的bytes类型的形式
    len_dic = struct.unpack('i',len_dic)
    dic = conn.recv(len_dic[0])#只接收字典大小的容量,防止粘包,节约了时间
    re = json.loads(dic)
    
    if re['men'] == 'upload':
        filename = '1' + re['filename']
        with open(filename , 'ab') as f:
            while re['filesize'] :
                content = conn.recv(1024)
                f.write(content)
                re['filesize'] = re['filesize'] - len(content)
    
    if re['men'] == 'download': #判断所接收的是不是下载功能
        print(conn)
        dic = {'men': re['men'], 'filename': ' ', 'filesize': ' '}
        filename = os.path.basename(re['file_path'])
        filesize = os.path.getsize(re['file_path'])
        dic['filename'] = filename
        dic['filesize'] = filesize
        str_dic = json.dumps(dic)
        conn.send(str_dic.encode('utf-8'))#第一次发送字典
        with open(re['file_path'], 'rb') as f:
            while filesize:
                content = f.read(1024)
                conn.send(content)#把要下载的内容发送给客户端
                filesize = filesize - len(content)
        print(dic)
    conn.close()
    sk.close()
    
    
    
    
    
    客户端
    
    import socket
    import os
    import json
    import struct
    sk = socket.socket()
    sk.connect(('127.0.0.1',8090))
    
    menu = {'1':'upload','2':'download'}
    for k , v in menu.items():
        print(k,v)
    choose = input('请选择功能:')
    
    if choose == '1':
        dic = {'men':menu.get(choose),'filename':' ','filesize':' '}
        file_path = input('请输入一个绝对路径:')
        filesize = os.path.getsize (file_path)#获取用户输入的文件路径下文件的大小
        filename = os.path.basename(file_path)#文件名字
        dic['filename'] = filename#把文件名封装到字典
        dic['filesize'] = filesize#把用户输入的文件大小封装到字典
        str_dic = json.dumps(dic)#字典序列化
        dic_size = len(str_dic)#字典大小
        b_dic_size = struct.pack('i',dic_size)
        sk.send(b_dic_size + str_dic.encode('utf-8'))#把字典的大小及其字典,发送给服务器,
        with open (file_path , 'rb' ) as f:
            while filesize :
                content = f.read(1024)#规定每行读取到的内容1024
                sk.send(content)#阶段发送
                filesize = filesize - len(content)#剩余文件大小减去发送的文件大小
    
    elif choose == '2':
        dic = {'men': menu.get(choose), 'file_path': ' ', 'filesize': ' '}
        dic['file_path'] = input('请输入一个绝对路径:')
        str_dic = json.dumps(dic)
        str_size = len(str_dic)
        b_dic_size = struct.pack('i',str_size)
        sk.send(b_dic_size + str_dic.encode('utf-8'))    #把封装后的字典发送给服务器
        dic = sk.recv(1024).decode('utf-8')#接收的是第一次服务器端发送的字典
        str_di = json.loads(dic)
        with open(str_di['filename']+'1' , 'ab' ) as f :
            while str_di['filesize'] :
                content = sk.recv(1024)
                f.write(content)   #把服务器端读出内容进行下载即是写入文件
                str_di['filesize'] = str_di['filesize'] - len(content)
    
    sk.close()
  • 相关阅读:
    开发一个App的成本是多少?
    自定义Drawable
    数据库服务软件类型和配置redis
    分库分表
    创建视图
    部署MYSQL高可用集群
    读写分离《二》
    读写分离和多实例
    部署mysql主从同步
    备份和恢复
  • 原文地址:https://www.cnblogs.com/panda-pandeyong/p/9474926.html
Copyright © 2011-2022 走看看