一什么是粘包
须知: 只有 TCP 有粘包现象, UDP 永远不会粘包
粘包不一定会发生
如果发生了: 1. 可能是在客户端已经粘了
2. 客户端没有粘, 可能是在服务端粘了
首先需要掌握一个 socket 收发消息的原理
应用程序所看到的数据是一个整体, 或说是一个流 (stream), 一条消息有多少字节对应用程序是不可见的, 因此 TCP 协议是面向流的协议, 这也是容易出现粘包问题的原因(因为 TCP 是流式协议, 不知道啥时候开始, 啥时候结束) 而 UDP 是面向消息的协议, 每个 UDP 段都是一条消息, 应用程序必须以消息为单位提取数据, 不能一次提取任意字节的数据, 这一点和 TCP 是很不同的怎样定义消息呢? 可以认为对方一次性 write/send 的数据为一个消息, 需要明白的是当对方 send 一条信息的时候, 无论底层怎样分段分片, TCP 协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区
所谓粘包问题主要还是因为接收方不知道消息之间的界限, 不知道一次性提取多少字节的数据所造成的
二发生粘包的两种情况
发送端需要等缓冲区满才发送出去, 造成粘包(发送数据时间间隔很短, 数据了很小, 会当做一个包发出去, 产生粘包)
- # 服务端
- from socket import *
- phone = socket(AF_INET,SOCK_STREAM)
- phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1)
- phone.bind((127.0.0.1,8080))
- phone.listen(5)
- print(start running...)
- coon,addr = phone.accept() #等待连接
- data1 = coon.recv(10)
- data2 = coon.recv(10)
- print(------------>,data1.decode(utf-8))
- print(------------>,data2.decode(utf-8))
- coon.close()
- phone.close()
- # 客户端
- from socket import *
- import time
- phone = socket(AF_INET,SOCK_STREAM)
- phone.connect((127.0.0.1,8080))
- phone.send(hello.encode(utf-8))
- phone.send(helloworld.encode(utf-8))
- phone.close()
接收方不及时接收缓冲区的包, 造成多个包接收(客户端发送了一段数据, 服务端只收了一小部分, 服务端下次再收的时候还是从缓冲区拿上次遗留的数据, 产生粘包)
- # 服务端
- from socket import *
- phone = socket(AF_INET,SOCK_STREAM)
- phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1)
- phone.bind((127.0.0.1,8080))
- phone.listen(5)
- print(start running...)
- coon,addr = phone.accept() #等待连接
- data1 = coon.recv(2) #一次没有接收完整
- data2 = coon.recv(10) #下一次接收的时候会先取旧的数据, 然后取新的
- # data3 = coon.recv(1024) #接收等 5 秒后的信息
- print(------------>,data1.decode(utf-8))
- print(------------>,data2.decode(utf-8))
- # print(------------>,data3.decode(utf-8))
- coon.close()
- phone.close()
- # 客户端
- from socket import *
- import time
- phone = socket(AF_INET,SOCK_STREAM)
- phone.connect((127.0.0.1,8080))
- phone.send(hello.encode(utf-8))
- time.sleep(5)
- phone.send(haiyan.encode(utf-8))
- phone.close()
三解决粘包的方法
问题的根源在于, 接收端不知道发送端将要传送的字节流的长度, 所以解决粘包的方法就是围绕, 如何让发送端在发送数据前, 把自己将要发送的字节流总大小让接收端知晓, 然后接收端来一个死循环接收完所有数据
- # 服务端
- import socket
- import subprocess
- import struct
- phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
- phone.bind((127.0.0.1,8080)) #绑定手机卡
- phone.listen(5) #阻塞的最大数
- print(start runing.....)
- while True: #链接循环
- coon,addr = phone.accept()# 等待接电话
- print(coon,addr)
- while True: #通信循环
- # 收发消息
- cmd = coon.recv(1024) #接收的最大数
print(接收的是:%s%cmd.decode(utf-8))
- #处理过程
- res = subprocess.Popen(cmd.decode(utf-8),shell = True,
- stdout=subprocess.PIPE, #标准输出
- stderr=subprocess.PIPE #标准错误
- )
- stdout = res.stdout.read()
- stderr = res.stderr.read()
- #先发报头(转成固定长度的 bytes 类型, 那么怎么转呢? 就用到了 struct 模块)
- #len(stdout) + len(stderr)# 统计数据的长度
- header = struct.pack(i,len(stdout)+len(stderr))# 制作报头
- coon.send(header)
- #再发命令的结果
- coon.send(stdout)
- coon.send(stderr)
- coon.close()
- phone.close()
- # 客户端
- import socket
- import struct
- phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
- phone.connect((127.0.0.1,8080)) #连接服
- while True:
- # 发收消息
cmd = input(请你输入命令>>:).strip()
- if not cmd:continue
- phone.send(cmd.encode(utf-8)) #发送
- #先收报头
- header_struct = phone.recv(4) #收四个
- unpack_res = struct.unpack(i,header_struct)
- total_size = unpack_res[0] #总长度
- #后收数据
- recv_size = 0
- total_data=b
- while recv_size<total_size: #循环的收
- recv_data = phone.recv(1024) #1024 只是一个最大的限制
- recv_size+=len(recv_data) #
- total_data+=recv_data #
print(返回的消息:%s%total_data.decode(gbk))
phone.close()
四解决粘包问题升级版: 完整的解决了
- # 服务端
- import socket
- import subprocess
- import struct
- import json
- phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
- phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
- phone.bind((127.0.0.1,8080)) #绑定手机卡
- phone.listen(5) #阻塞的最大数
- print(start runing.....)
- while True: #链接循环
- coon,addr = phone.accept()# 等待接电话
- print(coon,addr)
- while True: #通信循环
- # 收发消息
- cmd = coon.recv(1024) #接收的最大数
print(接收的是:%s%cmd.decode(utf-8))
- #处理过程
- res = subprocess.Popen(cmd.decode(utf-8),shell = True,
- stdout=subprocess.PIPE, #标准输出
- stderr=subprocess.PIPE #标准错误
- )
- stdout = res.stdout.read()
- stderr = res.stderr.read()
- # 制作报头
- header_dic = {
- total_size: len(stdout)+len(stderr), # 总共的大小
- filename: None,
- md5: None
- }
- header_json = json.dumps(header_dic) #字符串类型
- header_bytes = header_json.encode(utf-8) #转成 bytes 类型(但是长度是可变的)
- #先发报头的长度
- coon.send(struct.pack(i,len(header_bytes))) #发送固定长度的报头
- #再发报头
- coon.send(header_bytes)
- #最后发命令的结果
- coon.send(stdout)
- coon.send(stderr)
- coon.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 True:
- # 发收消息
cmd = input(请你输入命令>>:).strip()
- if not cmd:continue
- phone.send(cmd.encode(utf-8)) #发送
- #先收报头的长度
- header_len = struct.unpack(i,phone.recv(4))[0] #吧 bytes 类型的反解
- #在收报头
- header_bytes = phone.recv(header_len) #收过来的也是 bytes 类型
- header_json = header_bytes.decode(utf-8) #拿到 json 格式的字典
- header_dic = json.loads(header_json) #反序列化拿到字典了
- total_size = header_dic[total_size] #就拿到数据的总长度了
- #最后收数据
- recv_size = 0
- total_data=b
- while recv_size<total_size: #循环的收
- recv_data = phone.recv(1024) #1024 只是一个最大的限制
- recv_size+=len(recv_data) #有可能接收的不是 1024 个字节, 或许比 1024 多呢,
- # 那么接收的时候就接收不全, 所以还要加上接收的那个长度
- total_data+=recv_data #最终的结果
print(返回的消息:%s%total_data.decode(gbk))
phone.close()
五 struct 模块
- # 该模块可以把一个类型, 如数字, 转成固定长度的 bytes 类型
- import struct
- res = struct.pack(i,12345)
- print(res,len(res),type(res)) #长度是 4
- res2 = struct.pack(i,12345111)
- print(res,len(res),type(res2)) #长度也是 4
- unpack_res =struct.unpack(i,res2)
- print(unpack_res) #(12345111,)
- print(unpack_res[0]) #12345111
来源: http://www.bubuko.com/infodetail-2500778.html