python socket 设置通信协议(详解python中TCP协议中的粘包问题)
python socket 设置通信协议
详解python中TCP协议中的粘包问题TCP协议中的粘包问题
1.粘包现象
基于TCP实现一个简易远程cmd功能
|
#服务端 import socket import subprocess sever = socket.socket() sever.bind(( '127.0.0.1' , 33521 )) sever.listen() while True : client, address = sever.accept() while True : 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) except ConnectionResetError: print ( 'connect broken' ) client.close() break sever.close() #客户端 import socket client = socket.socket() client.connect(( '127.0.0.1' , 33521 )) while True : cmd = input ( '请输入指令(Q\q退出)>>:' ).strip().lower() if cmd = = 'q' : break client.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类型)
|
import struct s = 123456789 res = struct.pack( 'i' , s) print (res) res2 = struct.unpack( 'i' , res) print (res2) print (res2[ 0 ]) |
2.粘包的解决方案基本版
既然我们拿到了一个可以固定长度的办法,那么应用struct模块,可以固定长度了。
为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
|
#服务器端 import socket import subprocess import struct sever = socket.socket() sever.bind(( '127.0.0.1' , 33520 )) sever.listen() while True : client, address = sever.accept() while True : 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) except Exception as e: client.close() print ( '连接中断。。。。' ) break #客户端 import socket import struct client = socket.socket() client.connect(( '127.0.0.1' , 33520 )) while True : 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 < len_data: 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从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
|
#服务器端 import socket import subprocess import datetime import json import struct sever = socket.socket() sever.bind(( '127.0.0.1' , 33520 )) sever.listen() while True : client, address = sever.accept() while True : 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) except ConnectionResetError: print ( '客户端断开连接。。。' ) client.close() break #客户端 import socket import struct import json client = socket.socket() client.connect(( '127.0.0.1' , 33520 )) while True : 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_length: data = client.recv( 1024 ) length + = len (data) all_data + = data print ( '数据的总长度:%s' % data_length) #我的电脑是Windows系统,所以用gbk解码系统发出的信息 print (all_data.decode( 'gbk' )) |
总结:
1.TCP协议中,会产生粘包现象。粘包现象产生本质就是读取数据长度未知。
2.解决粘包现象本质就是处理读取数据长度。
3.报头的作用就是解决数据传输过程中数据长度怎么计算传达和传输其他额外信息的。
以上所述是小编给大家介绍的python中TCP协议中的粘包问题详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对开心学习网网站的支持!
原文链接:https://www.cnblogs.com/5j421/p/10574390.html
- python符串操作教程(Python3.5运算符操作实例详解)
- python线程池怎么设置(python自定义线程池控制线程数量的示例)
- python基础编程函数参数(详解Python 函数如何重载?)
- python怎么测试api接口(python接口自动化测试之接口数据依赖的实现方法)
- python包和模块管理(python的依赖管理的实现)
- python定时任务详解(使用Python做定时任务及时了解互联网动态)
- python函数操作大全(Python的高阶函数用法实例分析)
- python怎么取出列表(Python 利用切片从列表中取出一部分使用的方法)
- python语言提供的3种基本数据类型(详解Python3 基本数据类型)
- python创建列表并查询(python列表使用实现名字管理系统)
- mongodb python教程(python使用pymongo操作mongo的完整步骤)
- python快速数据分类(Python基于滑动平均思想实现缺失数据填充的方法)
- python中的reload(搞清楚 Python traceback的具体使用方法)
- python响应处理post请求(Python3模拟curl发送post请求操作示例)
- python的模块与包与库(Python 中包/模块的 `import` 操作代码)
- python统一支付接口(Python实现的微信支付方式总结三种方式)
- ()
- 800壮士拼死拖住30万日军 八佰 的真实历史,誓与阵地共存亡(800壮士拼死拖住30万日军)
- 演员陈创,火于 哮天犬 ,颠峰于 福贵 ,现状却令人唏嘘(演员陈创火于哮天犬)
- 幼小衔接-20以内看图读数 写数 数的组成练习题(幼小衔接-20以内看图读数)
- 你只要花上20天记单词,英语成绩就能从57提到100(你只要花上20天记单词)
- 夕云天际飞,亢龙化太极(夕云天际飞亢龙化太极)
热门推荐
- mybatis为什么还用mysql(关于MyBatis连接MySql8.0版本的配置问题)
- 微信小程序双人游戏横屏(微信小程序实现拼图游戏)
- 使用mysqldump命令来备份(linux使用mysqldump+expect+crontab实现mysql周期冷备份思路详解)
- 苹果微信小程序页面空白(iPhoneX安全区域Safe Area底部小黑条在微信小程序和H5的屏幕适配)
- php怎么修改单独一行数据(PHP实现批量修改文件名的方法示例)
- php与xml文件(PHP读取XML文件的方法实例总结DOMDocument及simplexml方法)
- ASP.NET参数化模糊查询
- 协程在python中怎么使用(python协程之动态添加任务的方法)
- 图解ftp服务器搭建(三分钟配置一个FTP服务器)
- SQL Server数据库备份的几种方式
排行榜
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9