粘包及其相关知识

  一, 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()

 

posted @ 2018-08-14 20:18  panda/勇  阅读(235)  评论(0编辑  收藏  举报