粘包问题

目录:

    粘包内存

    粘包原因

    远程执行系统命令

    解决粘包

    自定义报头

粘包内存(仅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")) 
posted @ 2018-11-05 11:28  ChuckXue  阅读(931)  评论(0编辑  收藏  举报