文章目录
底层原理分析:
数据流向:
不管是
recv()
还是send()
,都不是直接接收对方的数据,而是操作自己的操作系统内存。不是一个
send()
对应一个recv()
通信流程:
recv: wait data 耗时非常长 copy data send: copy data
Nagle算法:(减少网络IO,提高程序效率,同时会有粘包的问题)
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包更有效的发给对方,使用了优化算法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制,即面向流的通信是无消息保护边界的。
解决粘包问题:
简单版本:发送固定长度的报头(只包含数据长度信息)
-
服务器制作固定长度报头:
import struct res = struct.pack("i",1230) # i:int 4字节 print(res,type(res),len(res)) -------------------------------------- b'\xce\x04\x00\x00' <class 'bytes'> 4
-
客户端解包报头:
import struct recv = struct.unpack("i",res) print(recv,recv[0]) -------------------------------------- (1230,) 1230
终极版本:发送固定长度的报头(用字典封装,包含多种信息)
-
服务器发送报头:
import struct import json header_dic = { 'filename':'a.txt', 'md5':'xxxxxxx', 'total_size':6516161646513131 } # 序列化 header_json = json.dumps(header_dic) print(header_json,'\n',type(header_json)) header_bytes = header_json.encode('utf-8') print(header_bytes,'\n',type(header_bytes),'\n',len(header_bytes)) socket.send(struct.pack('i',len(header_bytes))) # 发送报头长度 --------------------------------------------- {"filename": "a.txt", "md5": "xxxxxxx", "total_size": 6516161646513131} <class 'str'> b'{"filename": "a.txt", "md5": "xxxxxxx", "total_size": 6516161646513131}' <class 'bytes'> 71
-
客户端接收报头和数据:
import struct import json obj = socket.recv(4) # 收报头长度 header_size = struct.unpack('i',obj) # 收报头 header_bytes = socket.recv(header_size) # 从报头中解析出真实数据的描述信息 # 反序列化 header_json = header_bytes.decode('utf-8') header_dic = json.loads(head_json) print(header_dic,'\n',type(header_dic)) # 拿到真实数据长度 total_size = header_dic['total_size'] # 接收真实数据 recv_size = 0 recv_data = b'' while recv_size < total_size: res = socket.recv(1024) recv_data += res recv_size += len(res)