TCP黏包问题
什么是黏包?什么情况下会出现黏包的情况?该如何避免黏包的情况?
首先来看一个例子
#服务端
import time
from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(("127.0.0.1",8180))
server.listen(5)
conn,addr = server.accept()
resl = conn.recv(1024)
print(resl.decode('utf-8')) #客户端
from socket import *
import time
client = socket(AF_INET,SOCK_STREAM)
client.connect(("127.0.0.1",8180))
client.send(b"")
client.send(b'world')
结果:
1234world #为什么会连在一起了
这种简单的情况其实也好解决:
#客户端
from socket import *
import time
client = socket(AF_INET,SOCK_STREAM)
client.connect(("127.0.0.1",8180))
client.send(b"")
client.send(b'world')
time.sleep(3) #用时间隔开就能不让它黏在一起
client.send(b'world')
执行结果:
12345
world
还可以这样:
import time
from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(("127.0.0.1",8180))
server.listen(5)
conn,addr = server.accept()
resl = conn.recv(5) #指定的字节数就不能黏在一起了
print(resl.decode('utf-8'))
res2 = conn.recv(5)
print(res2.decode('utf-8'))
我们可以猜测的:当包的大小有限,同时连续发送的时候就可能出现黏包的情况。
什么是黏包?
只有TCP有黏包现象,UDP永远不会黏包。
之前提到过,TCP是面向连接的通信方式,提供了顺序、可靠、不会重复的数据传输,而且不会加上边界。
这个就意味着,每一个要发送的信息,可能会被拆分成多份,每一份都会不多不少地正确地到达目的地,然后被重新拼装起来,传给正在等待地应用程序。
问题就出现了,传输地数据没有界限,当传输到另一台地机器的时候,从内存中取值的时候,取值的大小取决于recv地参数。
如果需要传输地数据比较大,那么recv肯定无法一次取完,在内存中有残留,这样就会出现黏包地现象。
还有就是为了提高效率,tcp协议在传输地过程中,会将数据包较小的和时间间距比较小的数据包一起发送,这样也会造成黏包。
黏包是如何产生的?
黏包出现的时候,表现在有可能并没有取到自己想要的包,为什么没有取到自己的包了?
难道是内存中没有吗?不是,是因为系统按顺序给你取,而上一个又没有取完,所以就可能取到别人的包,怎么解决了?
改变recv参数并不能从根本上解决这个问题,recv受制于当前系统可供支配的内存,如果传输的数据比内存大,这样也会产生黏包。
刚才说了,你之所以会取别人的,是别人没有取完,只要能保证每个人都取到自己的包并且不残留就可以了。
那为什么就不能取完了?还是TCP的性质决定了,没有边界,不知道取到那里为止,因此只要让它知道了边界的位置,问题也就解决了。
怎样解决黏包问题?
在传输之前,我们先给对方添加一个包头,这样它就知道取到那里了;
#服务方
from socket import *
import subprocess
import struct
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8888))
server.listen(5) while True:
conn,addr = server.accept()
print('收到来自%s的访问' % addr[0])
while True:
try:
cmd = conn.recv(1024)
cmd = cmd.decode("utf-8") #接受的是二进制的格式,因为下面需要执行这条消息,所以必须解码
ret = subprocess.Popen(cmd,shell=True,
stdout=subprocess.PIPE, #正确输出
stderr=subprocess.PIPE, #错误输出
)
stdout = ret.stdout.read()
stderr = ret.stderr.read() #发送之前先创建一个报头
total_size = len(stdout) + len(stderr)
#报头必须是定长,不然发过去,不知道边界也没有意义
header_size = struct.pack('i',total_size) #转化为可以直接传出的二进制,而且i就为4
print(header_size)
conn.send(header_size) conn.send(stdout)
conn.send(stderr) except ConnectionResetError:
break
conn.close()
server.close()
接收方:
#客户端
from socket import *
import struct
client = socket()
client.connect(('127.0.0.1',8888))
while True:
cmd = input(">>>").strip()
if len(cmd) == 0:continue
client.send(cmd.encode("utf-8"))
header_size = client.recv(4) #先接受报头,这样就能知道这个包的边界了
total_size = struct.unpack('i',header_size)[0] #返回的是一个元祖
fact_size = 0
fact = b''
while fact_size < total_size: #使用循环,只有这个包取完了,才能取下一个,这样就没有遗漏
data = client.recv(1024)
fact += data
fact_size += len(data)
print(fact.decode('gbk')) #由于实在window下面执行,所有返回的编码是GBK格式,也只能用GBK解开。
client.close()
上面的代码加上一个包头,已经解决了黏包问题,但是如果我想在包头之中添加其它信息,而struct只能接收数字参数,又该如何了?
我们可以先把需要传输的信息解出来,再在信息中提取边界。
升级版:
#服务端
from socket import *
import subprocess
import struct,json
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8888))
server.listen(5)
while True:
conn,addr = server.accept()
print('收到来自%s的访问' % addr[0])
while True:
try:
cmd = conn.recv(1024) #因为一般命令都不会太长,使用1024足矣
cmd = cmd.decode("utf-8")
ret = subprocess.Popen(cmd,shell=True,
stdout=subprocess.PIPE, #正确输出
stderr=subprocess.PIPE, #错误输出
)
stdout = ret.stdout.read()
stderr = ret.stderr.read() #发送之前先创建一个报头
total_size = len(stdout) + len(stderr)
#报头必须是定长,不然发过去,不知道边界也没有意义
# 先创建一个报头
header_dict = {'total_size':total_size, #封装的包头
"md5":78978900967890878,
"filename":"a.txt"
}
header_str = json.dumps(header_dict) #字典不能传输,先使用json转化为字符串
header_bytes = header_str.encode('utf-8') #转化成二进制格式,不然直接报错
header_size = struct.pack('i',len(header_bytes)) #第一层数据(包含包头大小)
conn.send(header_size) #发送大小给客户端
conn.send(header_bytes) #包头信息也要一并发过去
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
server.close()
为什么UDP不会出现黏包问题了?
UDP的数据报会保留边界,这就表示,数据是整个发送的,不会像面向连接的协议那样先被拆分成小块。自然就不会存在残留,所以不会出现黏包现象。
TCP黏包问题的更多相关文章
- netty]--最通用TCP黏包解决方案
netty]--最通用TCP黏包解决方案:LengthFieldBasedFrameDecoder和LengthFieldPrepender 2017年02月19日 15:02:11 惜暮 阅读数:1 ...
- Python网络编程基础 ❷ 基于upd的socket服务 TCP黏包现象
TCP的长连接 基于upd的socket服务 TCP黏包现象
- socketserver tcp黏包
socket (套接字) tcp(黏包现象原因) 传输中由于内核区缓冲机制(等待时间,文件大小),会在 发送端 缓冲区合并连续send的数据,也会出现在 接收端 缓冲区合并recv的数据给指定port ...
- python tcp黏包和struct模块解决方法,大文件传输方法及MD5校验
一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...
- 038.Python关于TCP黏包问题
黏包现象 1 黏包现象演示 服务端 #服务端 import socket sk = socket.socket() # 注册主机到网络 sk.bind( ("127.0.0.1", ...
- tcp黏包问题与udp为什么不黏包
1.先说下subprocess模块的用法,为了举个黏包的例子 # 通过一个例子 来认识网络编程中的一个重要的概念 # 所有的客户端执行server端下发的指令,执行完毕后,客户端将执行结果给返回给服务 ...
- Linux tcp黏包解决方案
tcpip协议使用"流式"(套接字)进行数据的传输,就是说它保证数据的可达以及数据抵达的顺序,但并不保证数据是否在你接收的时候就到达,特别是为了提高效率,充分利用带宽,底层会使用缓 ...
- tcp黏包
转载https://www.cnblogs.com/wade-luffy/p/6165671.html 无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. 回 ...
- struct 模块解决 TCP黏包问题
首先来看一下产生黏包现象的一段代码: # server.py 服务端 import socket sk = socket.socket() sk.bind(('127.0.0.1',9000)) ...
随机推荐
- Spring MVC 学习
一.基础 Spring MVC是当前最优秀的MVC框架,自从Spring 2.5版本发布后,由于支持注解配置,易用性有了大幅度的提高.Spring 3.0更加完善,实现了对Struts 2的超越.现在 ...
- How To Use Git Source Control with Xcode in iOS 6
This tutorial is by Malek Trabelsi, a passionate iOS developer from Tunisia focused primarily on mob ...
- RxJava Android(RxAndroid) 开发全家桶
RxJava 在 Android 应用开发中越来越流行,但是由于其门槛稍高,初次使用不免遇到很多问题,例如在 RxJava 常见的错误用法 和 不该使用 RxJava 的一些情况 中所描述的情况.为了 ...
- Ubuntu -- 配置Nginx和https及frp
使用 sudo apt -get nginx安装最方便. 要确认80端口未被占用. 上传域名的证书. 然后要配置nginx配置文件.
- [置顶]
一个简单好用的zabbix告警信息发送工具
之前使用邮件和短信发送zabbix告警信息,但告警信息无法实时查看或者无法发送,故障无法及时通知运维人员. 后来使用第三方微信接口发送信息,愉快地用了一年多,突然收费了. zabbix告警一直是我的痛 ...
- 如何限制Dedecms文章或产品描述的字数
在Dedecms系统中,文章摘要(可以通过infolen或description相关标签调用)被设置了字数上限为250字符,设置上限的主要目的是减少数据库的冗余,保证网站良好的性能.因此,如果对简介内 ...
- 【前端GUI】——对一些优秀网页设计作品的分析&心得
前言:优秀的网站设计作品都有一些相似的地方,即使是美学,也一定会遵循着一定的规律. ONE 这一组,属于同类. 主题:点心. 背景:卡通动物形象. 色调:柔和,甜美. 点线面布局: 在这两个页面中,点 ...
- Predicate与filter
转: http://blog.csdn.net/michaellufhl/article/details/6329823 怎么根据某些条件来过滤Collection的元素?我们可以在循环里面判断元素是 ...
- 微软Azure公有云个人用户是否能支付得起?
个人建立自己的站点是普通"白领"的愿望.由于,我们的大脑分泌的脑汁须要排泄渠道.怎么办呢? 依据微软公有云的公开报价(Pricing),租用单核CPU.0.75GB内存,20GB硬 ...
- vim 穿越时空
1. 回到以前的文件状态 :earlier 3m 回到文件3分钟之前的状态 2. 回到以后的文件状态 :later 3m 回到文件3分钟之后的状态 3. 时间单位 s 秒 m 分钟 d ...