粘包及其相关知识
一, 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()