只有TCP有粘包问题,而UDP永远不会粘包。我们先掌握一个socket收发消息的原理:
服务端可以1kb,1kb地发向客户端送数据,客户端的应用程序可以在缓存当中2kb,2kb地取走数据,当然也可以更多,或都更少。也就是说,应用程序 看到的数据是来个整体。或者说是一个流。一条消息有多少字节对应用程序是不可见的,TCP协议是面向流的协议,这就是它容易粘包的问题原因。
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一个消息要提取多少字节的数据所造成的。
此外,发送方引起的粘粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往收集到足够多的数据后才一个TCP段。若连续几次需要发送的数据都很少,通常TCP会根据(Nagle)优化算法,把这些数据合成一个TCP段后发出去,这样接收方就收到了粘包数据。
粘包情况一:发送端要等缓冲区满才发送出去(即发送的数据量少且时间间隔短,会合到一起),造成粘包。
1、服务端
from socket import * server=socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) conn,client_addr=server.accept() res1=conn.recv(1024) print('第一次:',res1) res2=conn.recv(1024) print('第二次:',res2) conn.close() server.close()
2、客户端
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.close()
先启动服务端,后再启动客户端,服务端得到的结果为:
粘包情况二:客户端发关了一段数据,服务端只收了一小部分,服务端下次再收的时候不是从缓冲区拿上次遗留的数据,产生粘包。
情况一的,客户端不变,服务端略作修改,如下:
from socket import * server=socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) conn,client_addr=server.accept() res1=conn.recv(2) print('第一次:',res1) res2=conn.recv(3) print('第二次:',res2) conn.close() server.close()先启动服务端,后再启动客户端,服务端得到的结果为:
现在我们来想一下如何处理粘包的方法。粘包的问题根源是接收端不知发送端将要传送的字节流,所以我们要让发送端在发送数据前,把要发送的字节流总大小让接收端知晓,然后接收端来个循环将其全部接收。这种方法比较低级,因为程序的运行速度运快于网络传输速度,所以在发送一段字节前,先发送该字节流的长度,这种方式会放大网络延迟带来的性能损耗。
目前比较合理的处理方法是:为字节流加上一个报头,将这个报告做成字典,字典里包含将要发送的真实数据详细信息。然后将这个字典JSON序列化,然后用struck将序列化后的数据长度打包成4个字节(4个字节完全够用)
发送时:先发报头长度,再编码报头内容然后发送,最后发真实数据。
接收时:用struct取出报头长度,然后根据长度取出报头,解码,反序列化。最后从反序列化的结果中取出待取数据的详细信息,然后取真实的数据内容。
来看实现的代码:
1、服务端
from socket import * import subprocess import struct import json server=socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn,client_addr=server.accept() print(conn,client_addr)#(连接对象,客户端的ip和端口) while True: try: cmd=conn.recv(1024) obj=subprocess.Popen( cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout=obj.stdout.read() stderr=obj.stderr.read() #1、制作报头 header_dic={ 'total_size':len(stdout)+len(stderr), 'md5':'dgdsfsdfdsdfsfewrewge', 'file_name':'a.txt' } header_json=json.dumps(header_dic) header_bytes=header_json.encode('utf-8') #2、先发送报头的长度 header_size=len(header_bytes) conn.send(struct.pack('i',header_size)) #3、发送报头 conn.send(header_bytes) #4、发送真实的数据 conn.send(stdout) conn.send(stderr) except ConnectionResetError: break conn.close() server.close()
2、客户端
from socket import * import json import struct client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: cmd=input(">>:").strip() if not cmd:continue client.send(cmd.encode('utf-8')) #1、接收报文头的长度 header_size=struct.unpack('i',client.recv(4))[0] #2、接收报文 header_bytes=client.recv(header_size) #3、解析报文 header_json=header_bytes.decode('utf-8') header_dic=json.loads(header_json) print(header_dic) #4、获取真实数据的长度 totol_size=header_dic['total_size'] #5、获取数据 recv_size=0 res=b'' while recv_size<totol_size: recv_date=client.recv(1024) res+=recv_date recv_size+=len(recv_date) print(res.decode('gbk')) client.close()