tcp协议下粘包问题的产生及解决方案
1 tcp有粘包及udp无粘包
- TCP 是面向连接的,面向流的可靠协议;发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,
合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制面向流的通信是无消息保护边界的。
- UDP(用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来
记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
注:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,
而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头
2 产生原因:
1、接收端不知道消息的界限,不知道一次提取多少字节数据
2、TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,
通常TCP会根据优化算法(Nagle算法)把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据
产生粘包场景:(1)发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
(2)接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
3 解决方案
为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
struct:
注:struct 模块 把一个数字类型转化为固定长度的bytes
(struct.pack)打包 (struct.unpack)解包
res=(struct.pack('i',4855524)) #b'\x04\xe6\xe4\x02' 打包
print(res)
print(struct .unpack('i',res)[0]) #解包
服务端端:
import subprocess
import socket
import struct
import json
phone= socket.socket(socket.AF_INET ,socket.SOCK_STREAM )
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True :
conn,client=phone.accept()
while True :
try:
cmd = conn.recv(1024)
if len(cmd) == 0: break
# 远程执行命令
obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, # 解码
stdout=subprocess.PIPE, # 正确信息
stderr=subprocess.PIPE # 错误信息
)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
#先制作报头
head_dic= {'filename':'a.txt',
'total_size':len(stdout)+len(stderr),
'hash':'asdf165485221'
}
head_json = json.dumps(head_dic)
head_bytes= head_json.encode('utf-8')
#1、先把报头的长度打包成四个bytes,然后发送
conn.send(struct.pack('i',len(head_bytes))) #2、发送报头
conn.send(head_bytes)
#3、发送真实数据
conn.send(stdout )
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
phone.close()
客户端:
import struct
import socket
import json
phone= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone. connect(('127.0.0.1',8080))
while True :
msg = input('<<<')
if msg == 0:continue
#phone.send(msg.encode('utf-8'))
phone.send(bytes(msg,encoding='utf-8'))
#1、先收4个字节,该4个字节包含报头的长度 解包
header_len=struct .unpack('i',phone.recv(4))[0]
#2、通过报头长度,再接受报头内容
header_bytes=phone.recv(header_len) #通过报头长度,拿到bytes内容
#从报头中解析出想要的内容
header_json=header_bytes .decode('utf-8') #报头内容解码得到字符串类型
header_dic=json .loads(header_json) #反序列化得到字典
print(header_dic)
total_size = header_dic['total_size']
#3、再收真实的数据
recv_size =0 #初始值长度
res=b'' #接收的具体值
while recv_size< total_size:
data= phone.recv(1024)
res+=data # 拼接具体的值
recv_size += len(data) #累加长度
print(res.decode('gbk')) #收到的信息用GBK解码
phone.close()
tcp协议下粘包问题的产生及解决方案的更多相关文章
- 基于tcp协议下粘包现象和解决方案,socketserver
一.缓冲区 每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区.write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送 ...
- 网络编程之tcp协议以及粘包问题
网络编程tcp协议与socket以及单例的补充 一.单例补充 实现单列的几种方式 #方式一:classmethod # class Singleton: # # __instance = None # ...
- 为什么 TCP 协议有粘包问题
为什么 TCP 协议有粘包问题 这部分转载自draveness博客. TCP/IP 协议簇建立了互联网中通信协议的概念模型,该协议簇中的两个主要协议就是 TCP 和 IP 协议.TCP/ IP 协议簇 ...
- 基于tcp协议的粘包问题(subprocess、struct)
要点: 报头 固定长度bytes类型 1.粘包现象 粘包就是在获取数据时,出现数据的内容不是本应该接收的数据,如:对方第一次发送hello,第二次发送world,我放接收时,应该收两次,一次是hel ...
- TCP协议的粘包问题(八)
一.什么是粘包 在socket缓冲区和数据的传递过程介绍中,可以看到数据的接收和发送是无关的,read()/recv() 函数不管数据发送了多少次,都会尽可能多的接收数据.也就是说,read()/re ...
- TCP协议的粘包现象和解决方法
# 粘包现象 # serverimport socket sk = socket.socket()sk.bind(('127.0.0.1', 8005))sk.listen() conn, addr ...
- [Swoole系列入门教程 6] TCP协议和粘包
- TCP通讯处理粘包详解
TCP通讯处理粘包详解 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的 ...
- Netty(三) 什么是 TCP 拆、粘包?如何解决?
前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...
随机推荐
- 正则表达式过滤html标签
1.说明:需要使用非贪婪模式 2.示例 过滤所有span标签: var newContent = Regex.Replace(htmlContent, "<span.*?>.*? ...
- requests禁止重定向
response = requests.get( 'http://weixin.sogou.com/weixin?query=%E9%A3%8E%E6%99%AF&type=2&pag ...
- mongodb数据中的复制(副本集)
---恢复内容开始--- 什么是复制 复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性,并可以保证数据的安全性 复制还允许从硬件故障和服务中断中恢复数据 为什么要复制 数据备 ...
- 天财商龙SQL查询ID登陆POS情况
SELECT * FROM [tcgem_client0001].[dbo].[TCB_POS] ' --查询ID登陆POS情况
- Leetcode 344. 反转字符串
344. Reverse String 解题代码: class Solution { public: void reverseString(vector<char>& s) { , ...
- snmp对超过16T的磁盘大小识别不对的解决办法
https://blog.csdn.net/redleaf0000/article/details/38303299
- [转] Mongoose初使用总结
连接mongoose mongoose连接数据库有两种方式 第一种: 'use strict'; const mongoose = require('mongoose'); mongoose.conn ...
- mysql中trim()函数的用法
去除左空格函数: LTRIM(str) mysql> SELECT LTRIM(' barbar'); -> 'barbar' 去除右空格函数: RTRIM(str) mysql> ...
- 51Nod1231 记分牌 动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1231.html 题目传送门 - 51Nod1231 题意 题解 显然是一个竞赛图相关的题. 我们首先 ...
- BZOJ3862 Little Devil I 树链剖分
原文链接http://www.cnblogs.com/zhouzhendong/p/8081514.html 题目传送门 - BZOJ3862 题意概括 一棵树,n个点,边权为黑或者白,支持3重操作: ...