黏包现象.
socket缓冲区
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。
read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
缓冲区特性
I/O缓冲区在每个TCP套接字中单独存在
I/O缓冲区在创建套接字时自动生成
即是关闭套接字也会继续传送输出缓冲区中遗留的数据
关闭套接字将丢失输入缓冲区中的数据
缓冲区默认大小8K,可以通过setsocket()函数获取:
import socket server = socket.socket() server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 重用ip地址和端口 server.bind(('127.0.0.1',8010)) server.listen(3) print(server.getsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF)) # 输出缓冲区大小 print(server.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF)) # 输入缓冲区大小
只有TCP有黏包现象,DUP永远不会黏包
- 黏包问题主要还是因为接受方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的
黏包产生情况
- 接受方没有及时接收缓冲区的包,造成多个包接受(客户端发生了一段数据,服务端只收了一部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生黏包)
- 发送端需要等缓冲区满才发送出去,造成黏包(发送数据时间间隔很短,数据也很小,会合到一起,产生黏包)
黏包解决方法
利用struct模块.将一个类型转成固定长度的bytes
import struct # 将一个数字转化成等长度的bytes类型。 ret = struct.pack('i', 183346) print(ret, type(ret), len(ret)) # 通过unpack反解回来 ret1 = struct.unpack('i',ret)[0] print(ret1, type(ret1), len(ret1)) # 但是通过struct 处理不能处理太大 ret = struct.pack('l', 4323241232132324) print(ret, type(ret), len(ret)) # 报错
方法1:low版
将要发送的字节流总数按照固定的字节发送给接收端,然后再将数据发送过去,在用死循环将数据读完.
服务端
import struct phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.bind(('127.0.0.1', 8080)) phone.listen(5) while 1: conn, client_addr = phone.accept() print(client_addr) while 1: try: cmd = conn.recv(1024) ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) correct_msg = ret.stdout.read() error_msg = ret.stderr.read() # 1 制作固定报头 total_size = len(correct_msg) + len(error_msg) header = struct.pack('i', total_size) # 2 发送报头 conn.send(header) # 发送真实数据: conn.send(correct_msg) conn.send(error_msg) except ConnectionResetError: break conn.close() phone.close() # 但是low版本有问题: # 1,报头不只有总数据大小,而是还应该有MD5数据,文件名等等一些数据。 # 2,通过struct模块直接数据处理,不能处理太大。
客户端:
import socket import struct phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while 1: cmd = input('>>>').strip() if not cmd: continue phone.send(cmd.encode('utf-8')) # 1,接收固定报头 header = phone.recv(4) # 2,解析报头 total_size = struct.unpack('i', header)[0] # 3,根据报头信息,接收真实数据 recv_size = 0 res = b'' while recv_size < total_size: recv_data = phone.recv(1024) res += recv_data recv_size += len(recv_data) print(res.decode('gbk')) phone.close()
旗舰版:自定制报头
整体流程
整个流程的大致解释: 我们可以把报头做成字典,字典里包含将要发送的真实数据的描述信息(大小啊之类的),然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。 我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的,看一下 发送时: 先发报头长度 再编码报头内容然后发送 最后发真实内容 接收时: 先手报头长度,用struct取出来 根据取出的长度收取报头内容,然后解码,反序列化 从反序列化的结果中取出待取数据的描述信息,然后去取真实的数据内容
服务端
import socket import subprocess import struct import json phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.bind(('127.0.0.1', 8080)) phone.listen(5) while 1: conn, client_addr = phone.accept() print(client_addr) while 1: try: cmd = conn.recv(1024) ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) correct_msg = ret.stdout.read() error_msg = ret.stderr.read() # 1 制作固定报头 total_size = len(correct_msg) + len(error_msg) header_dict = { 'md5': 'fdsaf2143254f', 'file_name': 'f1.txt', 'total_size':total_size, } header_dict_json = json.dumps(header_dict) # str bytes_headers = header_dict_json.encode('utf-8') header_size = len(bytes_headers) header = struct.pack('i', header_size) # 2 发送报头长度 conn.send(header) # 3 发送报头 conn.send(bytes_headers) # 4 发送真实数据: conn.send(correct_msg) conn.send(error_msg) except ConnectionResetError: break conn.close() phone.close()
客户端
import socket import struct import json phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while 1: cmd = input('>>>').strip() if not cmd: continue phone.send(cmd.encode('utf-8')) # 1,接收固定报头 header_size = struct.unpack('i', phone.recv(4))[0] # 2,解析报头长度 header_bytes = phone.recv(header_size) header_dict = json.loads(header_bytes.decode('utf-8')) # 3,收取报头 total_size = header_dict['total_size'] # 3,根据报头信息,接收真实数据 recv_size = 0 res = b'' while recv_size < total_size: recv_data = phone.recv(1024) res += recv_data recv_size += len(recv_data) print(res.decode('gbk')) phone.close()