一、什么是TCP粘包
C/S架构下,接收方不知道每个消息的发送间隙、也不知道每次应该提取多少个字节的数据,与此同时,TCP是面向连接的,面向流的,收发两端都要有,因此发送端为了将多个发往接收端的数据包更高效的发给对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个发送给接收端。此时接收端无法分辨出来,必须提供合理的拆包机制,即面向流的通信是无消息保护边界的。
除此之外,因为TCP是基于流的,所以收发的消息不能为空,需要发送、接收端添加空消息处理机制,防止程序卡住。
二、处理思路
粘包现象主要是因为发送端没有确切的发送间隔以及发送数据包的大小,而接收端更不知道发送算发送了多少个数据包以及大小,只能来多少接收多少。
解决方法:
自定义报头,发送端发送数据之前,先将自定义的报头(数据包大小等信息)发送给接收端,接收端明确每个数据包的大小进行依次接收。
三、实现方式
方法一:
1 # 客户端 2 import socket 3 import struct 4 5 IP = '127.0.0.1' 6 PORT = 8080 7 bufsize = 1024 8 9 client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 10 client_socket.connect((IP,PORT)) 11 12 while True: 13 cmd = input('cmd>>>').strip() 14 if len(cmd) == 0:continue 15 elif cmd == 'q':break 16 17 client_socket.send(cmd.encode('utf-8')) 18 19 # 1.接收固定报头 20 header = client_socket.recv(4) 21 22 # 2.解析报头 23 total_size = struct.unpack('i',header)[0] 24 print(total_size) 25 26 # 3.根据包头接收真实数据 27 recv_size = 0 28 # 保存接收的数据(接收到的是byte类型) 29 res_data = b'' 30 while recv_size < total_size: 31 recv_data = client_socket.recv(1024) 32 res_data += recv_data 33 recv_size += len(recv_data) 34 35 print(recv_data.decode('gbk')) 36 37 client_socket.close() 38 39 # 服务端 40 import socket 41 import subprocess 42 import struct 43 44 IP = '127.0.0.1' 45 PORT = 8080 46 bufsize = 1024 47 48 tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 49 tcp_socket.bind((IP,PORT),) 50 tcp_socket.listen(5) 51 52 while True: 53 conn,addr = tcp_socket.accept() 54 print('客户端:',addr) 55 56 while True: 57 try: 58 cmd = conn.recv(bufsize) 59 res = subprocess.Popen(cmd.decode('utf-8'),shell=True 60 ,stdin=subprocess.PIPE 61 ,stdout=subprocess.PIPE 62 ,stderr=subprocess.PIPE) 63 stderr = res.stderr.read() 64 stdout = res.stdout.read() 65 66 67 # 1.制作固定长度的报头 68 total_size = len(stdout) + len(stderr) 69 header = struct.pack('i',total_size) 70 71 # 2.发送报头 72 conn.send(header) 73 74 # 3.发送真实数据 75 conn.send(stderr) 76 conn.send(stdout) 77 except ConnectionResetError: 78 break 79 conn.close() 80 tcp_socket.close()
方法二:
1 # 客户端 2 import socket 3 import struct 4 import json 5 6 IP = '127.0.0.1' 7 PORT = 8080 8 bufsize = 1024 9 10 client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 11 client_socket.connect((IP,PORT)) 12 13 while True: 14 cmd = input('cmd>>>').strip() 15 if len(cmd) == 0:continue 16 elif cmd == 'q':break 17 18 client_socket.send(cmd.encode('utf-8')) 19 20 # 1.接收包头长度 21 header_size = struct.unpack('i',client_socket.recv(4))[0] 22 23 # 2.接收报头 24 header_bytes = client_socket.recv(header_size) 25 26 # 3.解析报头 27 header_json = header_bytes.decode('utf-8') 28 header_dic = json.loads(header_json) 29 # print(header_dic) 30 31 total_size = header_dic['total_size'] 32 33 # 3.根据包头接收真实数据 34 recv_size = 0 35 # 保存接收的数据(接收到的是byte类型) 36 res_data = b'' 37 while recv_size < total_size: 38 recv_data = client_socket.recv(1024) 39 res_data += recv_data 40 recv_size += len(recv_data) 41 42 print(recv_data.decode('gbk')) 43 44 client_socket.close() 45 46 47 48 # 服务端 49 import socket 50 import subprocess 51 import struct 52 import json 53 54 IP = '127.0.0.1' 55 PORT = 8080 56 bufsize = 1024 57 58 tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 59 tcp_socket.bind((IP,PORT),) 60 tcp_socket.listen(5) 61 62 while True: 63 conn,addr = tcp_socket.accept() 64 print('客户端:',addr) 65 66 while True: 67 try: 68 cmd = conn.recv(bufsize) 69 res = subprocess.Popen(cmd.decode('utf-8'),shell=True 70 ,stdin=subprocess.PIPE 71 ,stdout=subprocess.PIPE 72 ,stderr=subprocess.PIPE) 73 stderr = res.stderr.read() 74 stdout = res.stdout.read() 75 76 # 1.制作固定长度的报头 77 header_dic = { 78 'total_size':len(stdout) + len(stderr), 79 'md5':'123sssss222', 80 'filename':'120.txt'} 81 82 header_json = json.dumps(header_dic) 83 header_bytes = header_json.encode('utf-8') 84 85 # 2.发送报头的长度 86 total_size = len(header_bytes) 87 conn.send(struct.pack('i',total_size)) 88 89 # 发送报头 90 conn.send(header_bytes) 91 92 # 3.发送真实数据 93 conn.send(stderr) 94 conn.send(stdout) 95 except ConnectionResetError: 96 break 97 conn.close() 98 tcp_socket.close()