存在于客户端接收数据时,不能一次性收取全部缓冲区中的数据.当下一次再有数据来时,缓冲区中剩余的数据会和新的数据'粘连'在一起.这就是粘包现象
### 什么是粘包? # 存在于TCP/IP协议中数据粘连在一起, ### socket中造成粘包现象的原因是什么? # 客户端不能一次性接收完缓冲区里的所有数据, # 服务端接受客户端发送数据时,由于缓冲区没有存满,回先把数据'聚合'在客户端的缓冲区中,当客户端缓存区存满之后,在发给服务端 ### 那些情况会发生粘包现象? # 1. 客户端多次send少量数据, 会在客户端send输出缓冲区堆积, 当缓冲区堆积到一定程度 .会一次性把缓冲区的数据发给服务端 # 造成粘包现象 # 2. 客户端接收服务端发送的数据,会在客户端的recv输入缓存堆积,由于每次接收数据有限,当第二次服务端把数据发送过来时 # 数据会堆积在客户端的输入缓冲区.造成粘包如下图:?
1.每个socket被创建后,都会分配两个缓冲区,输入缓冲区(recv)和输出缓冲区(send)
2.send和recv都不是直接从网络中直接读取数据,而是从各自缓冲区读入和输出数据.
3.数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制
a、提高执行效率。 协调数据的收发(接受和处理)速度 b、减少内存负担。 减少和磁盘的交互 c、保持数据稳定性
一:客户端(recv)不能一次性读取缓冲区中的数据.服务端发送的数据大于客户端能够接受的数据量,客户端下次接收的时候先去接收上次遗留在缓冲区中的数据.造成粘包
代码如下:?
### 服务端 # -*-coding:utf-8-*- # Author:Ds import socket import subprocess 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() conn.send(correct_msg + error_msg) except ConnectionResetError: break conn.close() phone.close() #### 客户端 # -*-coding:utf-8-*- # Author:Ds import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买电话 phone.connect(('127.0.0.1',8080)) # 与客户端建立连接, 拨号 while 1: cmd = input('>>>') phone.send(cmd.encode('utf-8')) from_server_data = phone.recv(1024) print(from_server_data.decode('gbk')) phone.close() # 由于客户端发的命令获取的结果大小已经超过1024,那么下次在输入命令,会继续取上次残留到缓存区的数据二:客户端(send)向服务端发送数据的时候,发送多条少量数据. 由于缓冲区的没有存满,缓冲区会把这些数据聚集在一起发送个服务端.造成粘包
代码如下:?
### 服务端 # -*-coding:utf-8-*- # Author:Ds import socket server=socket.socket() server.bind(('127.0.0.1',7878)) server.listen(5) conn ,addr = server.accept() print(conn,addr) data1=conn.recv(1024) data2=conn.recv(1024) data=data1+data2 print(data.decode('utf-8')) conn.close() server.close() #### 客户端 # -*-coding:utf-8-*- # Author:Ds import socket client=socket.socket() client.connect(('127.0.0.1',7878)) client.send('123'.encode('utf-8')) client.send('abc'.encode('utf-8')) client.close() ## 两次返送信息时间间隔太短,数据小,造成服务端一次收取
将一串数字转换成等长的字节.
格式多种: 常用 i
代码如下:?
# -*-coding:utf-8-*- # Author:Ds import struct # 'i' 的取值范围 -2147483648 <= number <= 2147483647 # i 模式 等长四位 , 将一个数字转化成等长度的bytes类型。 res=struct.pack('i',123456) print(res,len(res)) ### 通过unpack反解回来 data=struct.unpack('i',res) print(data) ### 但是通过struct 处理不能处理太大 ret = struct.pack('q', 4323241232132324) print(ret, type(ret), len(ret)) # 报错 案例代码,如下?:
### 客户端 # -*-coding:utf-8-*- # Author:Ds import socket import struct import json client = socket.socket() # 实例化 socket对象 client.connect(('127.0.0.1', 8898)) # socket 连接IP和端口 while 1: ui = input('请输入指令:>>').strip() # 1. 发送 用户输入 指令 client.send(ui.encode('utf-8')) #2. 接收 struct 封装的头 字节形式 , 接收4个字节 head head = client.recv(4) # 3. struct反解 ,获得自定义报头的长度 dic_length = struct.unpack('i', head)[0] #4. 接收自定义字典报头内容 字节形式 head_dic = client.recv(int(dic_length)) #5. 反序列化自定义字典,先解码在反序列化 dic = json.loads(head_dic.decode('utf-8')) #6. 得到 真实内容的长度 content_length = dic['size'] # 7 设置一个字节变量 用于接收真实数据 content = b'' # 8 设置一个客户端接收长度 recv_size = 0 # 9 当客户端接收长度 小于 源数据长度,一直接收 while recv_size < content_length: # 累加 真实数据,以字节形式 content += client.recv(1024) # 累加 客户端接收的长度 recv_size += len(content) # 接收完毕,解码内容 print(content.decode('utf-8')) client.close() ### 服务端 # -*-coding:utf-8-*- # Author:Ds import socket import struct import json import time import subprocess server = socket.socket() # 实例化 socket对象 server.bind(('127.0.0.1', 8898)) # 绑定ip和端口 server.listen(5) # 邦迪监听连接 while 1: conn, addr = server.accept() # 等待连接 print(conn, addr) # 打印连接,和连接ip while 1: try: # 1. 接收客户端发送的 指令 ,字节形式 cmd = conn.recv(1024) # 2. 执行指令,得到结果 , 指令是字符形式(需要解码) obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) #3.得到结果字节 ,转换成utf-8的字节格式 result = (obj.stdout.read() + obj.stderr.read()).decode('gbk').encode('utf-8') #4. 获得真实数据的字节长度 total_res_bytes = len(result) #5. 自定制字典报头 head_dic = { 'time': time.localtime(time.time()), 'size': total_res_bytes, # 字节长度 'MD5': 'XXXXX', 'file_name': '婚前视频', } # 6. 序列化字典 ,并将其转换成字节形式 head_dic_bytes = json.dumps(head_dic).encode('utf-8') #7. 使用 struct 封装报头字典head_dic_bytes ,固定长度(4个字节) # 封装成字节,发送给客户端,还是按照字节取出来. head = struct.pack('i', len(head_dic_bytes)) # 8 , 先将固定头发送给客户端 conn.send(head) # 9 . 再将自定制报头发送给客户端 conn.send(head_dic_bytes) # 10. 最后将真实结果发送给客户端 conn.send(result) ### 这里就是拼接字节 # 格式: 固定头 + 自定义报头 +真实数据 except Exception: break conn.close() server.close()转载于:https://www.cnblogs.com/dengl/p/11202806.html
相关资源:Python进阶者笔记(粘包、粘包解决方案)