TCP协议中的粘包问题

1.粘包现象

基于TCP写一个远程cmd功能

#服务端

importsocketimportsubprocess

sever=socket.socket()

sever.bind(('127.0.0.1', 33521))

sever.listen()whileTrue:

client, address=sever.accept()whileTrue:try:

cmd= client.recv(1024).decode('utf-8')

p1= subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

data=p1.stdout.read()

err_data=p1.stderr.read()

client.send(data)

client.send(err_data)exceptConnectionResetError:print('connect broken')

client.close()breaksever.close()

​#客户端

importsocket

client=socket.socket()

client.connect(('127.0.0.1', 33521))whileTrue:

cmd= input('请输入指令(Q\q退出)>>:').strip().lower()if cmd == 'q':breakclient.send(cmd.encode('utf-8'))

data= client.recv(1024)print(data.decode('gbk'))

client.close()

上述是基于TCP协议的远程cmd简单功能,在运行时会发生粘包。

2、什么是粘包?

只有TCP会发生粘包现象,UDP协议永远不会发生粘包;

TCP:(transport control protocol,传输控制协议)流式协议。在socket中TCP协议是按照字节数进行数据的收发,数据的发送方发出的数据往往接收方不知道数据到底长度是多长,而TCP协议由于本身为了提高传输的效率,发送方往往需要收集到足够的数据才会进行发送。使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

UDP:(user datagram protocol,用户数据报协议)数据报协议。在socket中udp协议收发数据是以数据报为单位,服务端和客户端收发数据是以一个单位,所以不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。

TCP协议不会丢失数据,UDP协议会丢失数据。

udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

3、什么情况下会发生粘包?

1.由于TCP协议的优化算法,当单个数据包较小的时候,会等到缓冲区满才会发生数据包前后数据叠加在一起的情况。然后取的时候就分不清了到底是哪段数据,这是第一种粘包。

2.当发送的单个数据包较大超过缓冲区时,收数据方一次就只能取一部分的数据,下次再收数据方再收数据将会延续上次为接收数据。这是第二种粘包。

粘包的本质问题就是接收方不知道发送数据方一次到底发送了多少数据,解决问题的方向也是从控制数据长度着手,也就是如何设置缓冲区的问题

4、如何解决粘包问题?

解决问题思路:上述已经明确粘包的产生是因为接收数据时不知道数据的具体长度。所以我们应该先发送一段数据表明我们发送的数据长度,那么就不会产生数据没有发送或者没有收取完全的情况。

1.struct 模块(结构体)

struct模块的功能可以将python中的数据类型转换成C语言中的结构体(bytes类型)

importstruct

s= 123456789

#struct模块中的函数pack的作用是将python的数据转成bytes,第一个参数通常是i(返回值为4位bytes,表示C语言中的int型数据,),如果超界限了可以用q(返回值为8位bytes,表示C语言中的long long型)

res = struct.pack('i', s)print(res)

​#unpack是将字节转回整型,结果是一个元组

res2 = struct.unpack('i', res)print(res2)#取元组中的值

print(res2[0])

​###

b'\x15\xcd[\x07' #四位bytes

(123456789,) #元组

123456789 #初始整型数据

2.粘包的解决方案基本版

既然我们拿到了一个可以固定长度的办法,那么应用struct模块,可以固定长度了。

为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

#服务器端

importsocketimportsubprocessimportstruct

sever=socket.socket()

sever.bind(('127.0.0.1', 33520))

sever.listen()whileTrue:

client, address=sever.accept()whileTrue:try:

cmd= client.recv(1024).decode('utf-8')#利用子进程模块启动程序

p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)#管道输出的信息有正确和错误的

data =p.stdout.read()

err_data=p.stderr.read()#先将数据的长度发送给客户端

length = len(data)+len(err_data)#利用struct模块将数据的长度信息转化成固定的字节

len_data = struct.pack('i', length)#以下将信息传输给客户端

#1.数据的长度

client.send(len_data)#2.正确的数据

client.send(data)#2.错误管道的数据

client.send(err_data)exceptException as e:

client.close()print('连接中断。。。。')break​#客户端

importsocketimportstruct

client=socket.socket()

client.connect(('127.0.0.1', 33520))whileTrue:

cmd= input('请输入指令>>:').strip().encode('utf-8')

client.send(cmd)#1.先接收传过来数据的长度是多少,我们通过struct模块固定了字节长度为4

length = client.recv(4)#将struct的字节再转回去整型数字

len_data = struct.unpack('i', length)print(len_data)

len_data=len_data[0]print('数据长度为%s:' %len_data)

all_data= b''recv_size=0#2.接收真实的数据

#循环接收直到接收到数据的长度等于数据的真实长度(总长度)

while recv_size

data= client.recv(1024)

recv_size+=len(data)

all_data+=data

​print('接收长度%s' %recv_size)print(all_data.decode('gbk'))#总结:

服务器端:1.在服务器端先收到命令,打开子进程,然后计算返回的数据的长度2.先利用struct模块将数据长度转成固定4个字节传给客户端3.再向客户端发送真实的数据。

客户端(两次接收):1.第一次只接受4个字节,因为长度数据就是4个字节。这样防止了数据粘包。解码得到长度数据2.第二次循环接收真实数据,拼接真实数据完成解码读取数据。

很显然,如果仅仅只是这样肯定无法满足在实际生产中一些需求。那么该怎么修改?

我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个字节足够用了)

我们可以将自定义的报头设置成这种这种格式。

发送时:

1先发报头长度

2再编码报头内容然后发送

3最后发真实内容

接收时:

1先收报头长度,用struct取出来

2根据取出的长度收取报头内容,然后解码,反序列化

3从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容

#服务器端

importsocketimportsubprocessimportdatetimeimportjsonimportstruct

sever=socket.socket()

sever.bind(('127.0.0.1', 33520))

sever.listen()whileTrue:

client, address=sever.accept()whileTrue:try:

cmd= client.recv(1024).decode('utf-8')#启动子进程

p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)#得到子进程运行的数据

data = p.stdout.read() #子进程运行正确的输出管道数据,数据读出来后是字节

err_data = p.stderr.read() #子进程运行错误的输出管道数据

#计算数据的总长度

length = len(data) +len(err_data)print('数据总长度:%s' %length)

​#先需要发送报头信息,以下为创建报头信息(至第一次发送)

​#需要添加时间信息

time_info =datetime.datetime.now()#设置一个字典将一些额外的信息和长度信息放进去然后json序列化,报头字典

masthead ={}#将时间数据放入报头字典中

masthead['time'] = str(time_info) #时间格式不能被json序列化,所以将其转化为字符串形式

masthead['length'] =length

​#将报头字典json序列化

json_masthead = json.dumps(masthead) #得到json格式的报头

#将json格式的报头编码成字节形式

masthead_data = json_masthead.encode('utf-8')#利用struct将报头编码的字节的长度转成固定的字节(4个字节)

masthead_length = struct.pack('i', len(masthead_data))

​#1.发送报头的长度(第一次发送)

client.send(masthead_length)#2.发送报头信息(第二次发送)

client.send(masthead_data)#3.发送真实数据(第三次发送)

client.send(data)

client.send(err_data)exceptConnectionResetError:print('客户端断开连接。。。')

client.close()break

#客户端

importsocketimportstructimportjson

client=socket.socket()

client.connect(('127.0.0.1', 33520))whileTrue:

cmd= input('请输入cmd指令(Q\q退出)>>:').strip()if cmd == 'q':break​#发送CMD指令至服务器

client.send(cmd.encode('utf-8'))

​#1.第一次接收,接收报头信息的长度,由于struct模块固定长度为4字节,括号内直接填4

len_masthead = client.recv(4)#利用struct反解报头长度,由于是元组形式,取值得到整型数字masthead_length

masthead_length = struct.unpack('i', len_masthead)[0]

​#2.第二次接收,接收报头信息,接收长度为报头长度masthead_length 被编码成字节形式的json格式的字典,

#解字符编码得到json格式的字典masthead_data

masthead_data = client.recv(masthead_length).decode('utf-8')#得到报头字典masthead

masthead =json.loads(masthead_data)print('执行时间%s' % masthead['time'])#通过报头字典得到数据长度

data_length = masthead['length']

​#3.第三次接收,接收真实数据,真实数据长度为data_length

#data = client.recv(data_length) #有可能真实数据长度太大会撑爆内存。

#所以循环读取数据

all_data = b''length=0#循环直到长度大于等于数据长度

while length

data= client.recv(1024)

length+=len(data)

all_data+=dataprint('数据的总长度:%s' %data_length)

​#我的电脑是Windows系统,所以用gbk解码系统发出的信息

print(all_data.decode('gbk'))

python tcp处理_python中TCP粘包问题解决方案相关推荐

  1. netty中的粘包和半包

    在网络传输中,粘包和半包应该是最常出现的问题,作为 Java 中最常使用的 NIO 网络框架 Netty,它又是如何解决的呢?今天就让我们来看看. 一.定义 TCP 传输中,客户端发送数据,实际是把数 ...

  2. Linux_网络_传输层协议 TCP通信滑动窗口(快重传),流量控制,拥塞控制(慢启动),延迟应答,捎带应答,TCP常见问题(字节流,粘包),Listen半连接队列

    紧跟Linux_网络_传输层协议 TCP/UDP继续补充 文章目录 1. TCP通信时滑动窗口(效率) 2. 流量控制(可靠性) 3. 拥塞控制(慢启动) 4. 延迟应答 5. 捎带应答(提高通信效率 ...

  3. Netty4实战 - TCP粘包拆包解决方案

    Netty4实战 - TCP粘包&拆包解决方案 参考文章: (1)Netty4实战 - TCP粘包&拆包解决方案 (2)https://www.cnblogs.com/hunrry/p ...

  4. Netty 中的粘包和拆包详解

    Netty 底层是基于 TCP 协议来处理网络数据传输.我们知道 TCP 协议是面向字节流的协议,数据像流水一样在网络中传输那何来 "包" 的概念呢? TCP是四层协议不负责数据逻 ...

  5. 关于TCP/IOCP构架中出现的假死连接解决方案

    关于TCP/IOCP构架中出现的假死连接解决方案 参考文章: (1)关于TCP/IOCP构架中出现的假死连接解决方案 (2)https://www.cnblogs.com/max5945/p/5258 ...

  6. day27 粘包及粘包的解决方案

    1.   粘包现象 先了解一个词MTU MTU是Maximum Transmission Unit的缩写.意思是网络上传送的最大数据包.MTU的单位是字节. 大部分网络设备的MTU都是1500个字节, ...

  7. IO系列学习总结八:以Netty的聊天室程序为例,再聊聊拆包粘包的解决方案

    前言 在上篇文章:IO系列学习总结七:从官网Factorial协议的视角来理解出站.入站及数据碎片化(粘包拆包)中,针对Factorial协议做了一些解析,其中包含netty的入站出站处理顺序以及粘包 ...

  8. TCP中的粘包、拆包问题产生原因及解决方法

    目录 粘包/拆包 问题产生原因: 解决 粘包/拆包 问题: 为什么TCP有粘包? 为什么UDP没有粘包? 发生在网络的哪些层上? 粘包/拆包 问题产生原因: 发生TCP粘包或拆包有很多原因,现列出常见 ...

  9. [网络知识]TCP协议中的粘包与拆包

    在平时客户端socket开发中,如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题. 我们都知道TCP属于传输层 ...

最新文章

  1. 【响应式Web前端设计】CSS后代选择器和子代选择器
  2. 福利来了!国内TOP3的超级云计算,免费领2000核时计算资源!
  3. Colidity--GenomicRangeQuery
  4. bar图设置距离 python_python画图设置坐标轴的位置及角度及设置colorbar
  5. 特征工程(part5)--分类型变量
  6. [Linux]NIS: 集中化认证服务
  7. matlab频率阻抗,有分析阻抗的matlab脚本吗?
  8. 简单几何(四边形形状) UVA 11800 Determine the Shape
  9. Kotlin | Kotlin教程
  10. json数据循环左侧三级导航菜单
  11. 服务器装win7找不到硬盘驱动,电脑找不到硬盘驱动器,教你win7电脑找不到硬盘驱动器的解决方法...
  12. Spring学习笔记-IoC
  13. elasticsearch 深入 —— 地理位置
  14. Axure 8 网页滚动效果+APP上下垂直拖动效果
  15. 苹果闪退解决方法_《天涯明月刀手游》无限闪退问题解决方法 闪退是什么问题...
  16. 操作系统王道考研复习——第一章(计算机系统概述)
  17. greenplum数据库的使用
  18. java四叶玫瑰_[转载]java编程——四叶玫瑰线
  19. c语言书面作业,华软C语言书面作业14
  20. ARM汇编学习拾贝 (持续更新)

热门文章

  1. 开发合身的进销存管理软件(转)
  2. 敏捷团队Git分支版本管理策略| TBD++ Flow
  3. 达芬奇Configurator导入DBC初步
  4. NRF52832输出互补PWM
  5. (五)本地镜像发布到阿里云仓库以及私有库
  6. Java通过流的方式从OSS打压缩包下载或者直接下载文件,并返回输出流给前端(弹框选择下载路径)
  7. 关于拟认定为杨浦区第八批区块链企业的名单公示
  8. 敏捷开发的技术文档管理
  9. java基于springboot口腔牙科诊所管理系统
  10. 您是怎样进行数据销毁的硬盘销毁的?