粘包问题
目录:
粘包内存(仅TCP):
粘包原因:
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
TCP内部nagle算法,优化传输效率:数据量小且间隔时间小的数据会合并为一次进行发送
未解决前:
服务端:
from socket import *
server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
conn,addr=server.accept()
res1=conn.recv(1024)
print('第一次;',res1)
res2=conn.recv(1024)
print('第二次;',res2)
res3=conn.recv(1024)
print('第三次;',res3)
客户端:
from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('127.0.0.1', 8080)) client.send(b'hello') client.send(b'world') client.send(b'egon')
运行结果:
第一次; b'helloworldegon'#发生粘包问题 第二次; b'' 第三次; b''
解决之后:
#添加时间睡眠,减慢运行过程,完成分单次发送
服务端:
from socket import * server=socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) conn,addr=server.accept() res1=conn.recv(1024) print('第一次;',res1) res2=conn.recv(1024) print('第二次;',res2) res3=conn.recv(1024) print('第三次;',res3)
客户端:
from socket import * import time client = socket(AF_INET, SOCK_STREAM) client.connect(('127.0.0.1', 8080)) client.send(b'hello') time.sleep(0.2) client.send(b'world') time.sleep(0.2) client.send(b'egon')
打印结果:
第一次; b'hello' 第二次; b'world' 第三次; b'egon'
远程执行系统命令:
客户端:
1 from socket import * 2 import subprocess 3 import struct 4 5 server=socket(AF_INET,SOCK_STREAM) 6 server.bind(('127.0.0.1',8080)) 7 server.listen(5) 8 9 while True: 10 conn,client_addr=server.accept() 11 print('新的客户端',client_addr) 12 13 while True: 14 try: 15 cmd=conn.recv(1024) #cmd=b'dir' 16 if len(cmd) == 0:break 17 18 # 运行系统命令 19 obj=subprocess.Popen(cmd.decode('utf-8'), 20 shell=True,#命令解释器 21 stderr=subprocess.PIPE, 22 stdout=subprocess.PIPE 23 ) 24 25 stdout=obj.stdout.read() 26 stderr=obj.stderr.read() 27 28 #1、先制作报头(固定长度) 29 total_size=len(stdout) + len(stderr) 30 31 header=struct.pack('i',total_size) 32 33 #2、先发送固定长度的报头 34 conn.send(header) 35 36 #3、再发送真实的数据 37 conn.send(stdout) 38 conn.send(stderr) 39 except ConnectionResetError: 40 break 41 42 conn.close()
服务端:
1 from socket import * 2 import struct 3 4 client=socket(AF_INET,SOCK_STREAM) 5 client.connect(('127.0.0.1',8080)) 6 ''' 7 如果执行的命令返回的数据过多,一次接收不完,只能打印部分信息.(剩余的会放在系统缓冲区,会导致执行新命令的时候还会打印上次命令的信息), 8 所以需要分多次接收,接收到全部数据后,一次打印.从而避免不同的命令返回的信息发生粘包问题 9 ''' 10 while True: 11 cmd=input('>>: ').strip() 12 if len(cmd) == 0:continue#输空,重新发 13 client.send(cmd.encode('utf-8')) 14 15 #1、先收固定长度的报头 16 header=client.recv(4) 17 18 #2、从报头中解析出对数据的描述信息 19 total_size=struct.unpack('i',header)[0]#发送的数据的总长度 20 21 #3、再收真实的数据 22 recv_size=0 23 res=b'' 24 while recv_size < total_size :#接收到的数据的长度如果小于总长度,说明没有接收完 25 data=client.recv(1024) 26 res+=data 27 recv_size+=len(data) 28 29 print(res.decode('gbk'))#系统返回的为gbk
struct模块:
1 import struct 2 import json 3 4 header_dic={ 5 'filename':'a.txt', 6 'total_size':111123131232122222222221212121212121212211113122222222222222222222222222222222222222222222222, 7 'hash':'asdf123123x123213x' 8 } 9 10 header_json=json.dumps(header_dic) 11 12 header_bytes=header_json.encode('utf-8') 13 14 obj=struct.pack('i',len(header_bytes))#i模式会将任意数据变为四位 15 print(obj,len(obj))#b'\xfb\x01\x00\x00' 4
解决粘包:
服务端:
1 from socket import * 2 import subprocess 3 import struct 4 import json 5 6 server = socket(AF_INET, SOCK_STREAM) 7 server.bind(('127.0.0.1', 8080)) 8 server.listen(5) 9 10 while True: 11 conn, client_addr = server.accept() 12 print('新的客户端', client_addr) 13 14 while True: 15 try: 16 cmd = conn.recv(1024) # cmd=b'dir' 17 if len(cmd) == 0: break 18 # 运行系统命令 19 obj = subprocess.Popen(cmd.decode('utf-8'), 20 shell=True, 21 stderr=subprocess.PIPE, 22 stdout=subprocess.PIPE 23 ) 24 25 stdout = obj.stdout.read() 26 stderr = obj.stderr.read() 27 # 先制作报头,要包含发送数据的描述信息 28 header_dic = { 29 'filename': 'a.txt', 30 'total_size': len(stdout) + len(stderr), 31 'hash': 'xasf123213123' 32 } 33 header_json = json.dumps(header_dic) 34 header_bytes = header_json.encode('utf-8') 35 36 # 1、先把报头的长度len(header_bytes)打包成4个bytes,然后发送 37 conn.send(struct.pack('i', len(header_bytes))) 38 # 2、发送报头 39 conn.send(header_bytes) 40 # 3、再发送真实的数据 41 conn.send(stdout) 42 conn.send(stderr) 43 except ConnectionResetError: 44 break 45 46 conn.close()
客户端:
1 from socket import * 2 import struct 3 import json 4 5 client = socket(AF_INET, SOCK_STREAM) 6 client.connect(('127.0.0.1', 8080)) 7 8 while True: 9 cmd = input('>>: ').strip() 10 if len(cmd) == 0: continue 11 client.send(cmd.encode('utf-8')) 12 13 # 1、先收4个字节,该4个字节中包含报头的长度 14 header_len = struct.unpack('i', client.recv(4))[0] 15 16 # 2、再接收报头 17 header_bytes = client.recv(header_len) 18 19 # 从报头中解析出想要的内容 20 header_json = header_bytes.decode('utf-8') 21 header_dic = json.loads(header_json) 22 print(header_dic) 23 total_size = header_dic['total_size'] 24 25 # 3、再收真实的数据 26 recv_size = 0 27 res = b'' 28 while recv_size < total_size: 29 data = client.recv(1024) 30 res += data 31 recv_size += len(data) 32 33 print(res.decode('gbk'))
struct模块:
1 import struct 2 import json 3 4 header_dic={ 5 'filename':'a.txt', 6 'total_size':11112313123212222222222222222222222222222222222222222222222222222221111111111111111111111111111, 7 'hash':'asdf123123x123213x' 8 } 9 # print(len(header_dic)) 10 header_json=json.dumps(header_dic) 11 12 header_bytes=header_json.encode('utf-8') 13 print(len(header_bytes)) 14 obj=struct.pack('i',len(header_bytes)) 15 print(obj,len(obj)) 16 17 res=struct.unpack('i',obj) 18 print(res[0])
自定义报头:
1.先用报头传输数据的长度 对于我们远程CMD程序来说,只要先传输长度就能解决粘包的问题 但是如果做得是一个文件上传下载,除了数据的长度,还需要传输文件的名字/md5(用来判断接收的数据和发送时是否相同是否)等信息 2.自定义报头 完成发送一些额外的信息 例如文件名 1.将要发送的额外数据打包成一个字典 2.将字典转为bytes类型 3.计算字典的bytes长度 并先发送 4.发送字典数据 5.发送真实数据
代码:
服务端:
1 # 1.组装一个报头信息 2 head_dic = { 3 "name":"仓老师视频教学 如何做炸鸡!", 4 "md5":"asasasasaas", 5 "total_size":len(res), 6 "type":"video" 7 } 8 # 2.转json字符串 9 head_str = json.dumps(head_dic) 10 # 3.转字节 11 head_bytes = head_str.encode("utf-8") 12 # 4.发送报头长度 13 bytes_len = struct.pack("i",len(head_bytes)) 14 c.send(bytes_len) 15 # 5.发送报头 16 c.send(head_bytes) 17 # 6.发送真实数据 18 c.send(res)
客户端:
1 # 1.先获取报头长度 2 bytes_len = c.recv(4) #对方是i格式 固定4字节 3 # 2.转回整型 4 head_len = struct.unpack("i",bytes_len)[0] 5 6 # 3.接受报头数据 7 head_bytes = c.recv(head_len) 8 # 4.转为json字符串 并转为字典 9 head_dic = json.loads(head_bytes.decode("utf-8")) 10 print(head_dic) 11 12 # 已经接收的长度 13 recv_len = 0 14 # 一个表示最终数据的bytes 15 finally_data = b'' 16 # 3.收到的长度小于总长度就继续 17 while recv_len < head_dic["total_size"]: 18 # 循环收数据 19 data = c.recv(1024) 20 recv_len += len(data) 21 finally_data += data 22 # 整体解码 23 print(finally_data.decode("gbk"))