Python学习日记(三十一) 黏包问题
import subprocess res = subprocess.popen('dir',shell=true,stdout=subprocess.pipe,stderr=subprocess.pipe) print('stdout:',res.stdout.read().decode('gbk')) print('stderr:',res.stderr.read().decode('gbk'))
pipe把输出的东西装到一个'水管'里,如果在windows中的编码格式是gbk,执行结果:
stdout: 驱动器 c 中的卷是 系统 卷的序列号是 85c0-669a c:\users\administrator\pycharmprojects\internet_program 的目录 2019/09/16 13:48 <dir> . 2019/09/16 13:48 <dir> .. 2019/09/16 13:47 <dir> .idea 2019/09/16 13:46 21 client1.py 2019/09/16 13:42 0 client2.py 2019/09/16 13:48 207 sever1.py 2019/09/16 01:41 70 time_test.py 2019/09/14 23:51 <dir> venv 4 个文件 298 字节 4 个目录 45,863,636,992 可用字节 stderr:
在这里也可以使用os.popen()但是它会不管正确和错误的结果都放在一起,而用subprocess能够分别拿到正确和错误的信息
基于tcp实现的黏包
sever:
import socket sk = socket.socket() sk.bind(('127.0.0.1',8092)) sk.listen() conn,addr = sk.accept() while true: cmd = input('<<<') conn.send(cmd.encode('gbk')) ret = conn.recv(1024).decode('gbk') print(ret) conn.close() sk.close()
client:
import socket import subprocess sk = socket.socket() sk.connect(('127.0.0.1',8092)) while true: cmd = sk.recv(1024).decode('gbk') ret = subprocess.popen(cmd,shell=true,stdout=subprocess.pipe,stderr=subprocess.pipe) std_out = 'stdout:' + (ret.stdout.read()).decode('gbk') std_err = 'stderr:' + (ret.stderr.read()).decode('gbk') print(std_out) print(std_err) sk.send(std_out.encode('gbk')) sk.send(std_err.encode('gbk')) sk.close()
执行结果:
sever:
<<<dir;ls stdout: 驱动器 c 中的卷是 系统 卷的序列号是 85c0-669a c:\users\administrator\pycharmprojects\internet_program 的目录 <<<ipconfig stderr:找不到文件 <<<
client:
stdout: 驱动器 c 中的卷是 系统 卷的序列号是 85c0-669a c:\users\administrator\pycharmprojects\internet_program 的目录 stderr:找不到文件 stdout: windows ip 配置 以太网适配器 bluetooth 网络连接 2: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 dns 后缀 . . . . . . . : 以太网适配器 本地连接: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 dns 后缀 . . . . . . . : 无线局域网适配器 无线网络连接: 连接特定的 dns 后缀 . . . . . . . : 本地链接 ipv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:8018%14 ipv4 地址 . . . . . . . . . . . . : 192.168.43.216 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 192.168.43.1 隧道适配器 本地连接* 3: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 dns 后缀 . . . . . . . : stderr:
当我们在sever端输入dir;ls命令时,只有stdout的结果跑出来,而当我们输入ipconfig这个命令时,系统将上一次dir;ls未执行完的stderr的结果给跑出来。像这样没有接受完全或者接受多了的就是黏包现象。
tcp会有黏包现象但是它不丢包。
基于udp实现的黏包
sever:
import socket sk = socket.socket(type=socket.sock_dgram) sk.bind(('127.0.0.1',8092)) msg,addr = sk.recvfrom(10240) while 1: cmd = input('<<<') if cmd == 'q': break sk.sendto(cmd.encode('gbk'),addr) msg,addr = sk.recvfrom(10240) print(msg.decode('gbk')) sk.close()
client:
import socket import subprocess sk = socket.socket(type=socket.sock_dgram) addr = ('127.0.0.1',8092) sk.sendto('start'.encode('utf-8'),addr) while 1: cmd,addr = sk.recvfrom(10240) ret = subprocess.popen(cmd.decode('gbk'),shell=true,stderr=subprocess.pipe,stdout=subprocess.pipe) std_out = 'stdout:' + (ret.stdout.read()).decode('gbk') std_err = 'stderr:' + (ret.stderr.read()).decode('gbk') print(std_out) print(std_err) sk.sendto(std_out.encode('gbk'),addr) sk.sendto(std_err.encode('gbk'),addr) sk.close()
执行结果:
sever:
<<<dir;ls stdout: 驱动器 c 中的卷是 系统 卷的序列号是 85c0-669a c:\users\administrator\pycharmprojects\internet_program 的目录 <<<dir stderr:找不到文件 <<<ipconfig stdout: 驱动器 c 中的卷是 系统 卷的序列号是 85c0-669a c:\users\administrator\pycharmprojects\internet_program 的目录 2019/09/16 14:43 <dir> . 2019/09/16 14:43 <dir> .. 2019/09/16 14:37 <dir> .idea 2019/09/16 14:43 553 client1.py 2019/09/16 13:42 0 client2.py 2019/09/16 14:43 306 sever1.py 2019/09/16 01:41 70 time_test.py 2019/09/14 23:51 <dir> venv 4 个文件 929 字节 4 个目录 45,855,449,088 可用字节 <<<pwd stderr: <<<ip stdout: windows ip 配置 以太网适配器 bluetooth 网络连接 2: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 dns 后缀 . . . . . . . : 以太网适配器 本地连接: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 dns 后缀 . . . . . . . : 无线局域网适配器 无线网络连接: 连接特定的 dns 后缀 . . . . . . . : 本地链接 ipv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:8018%14 ipv4 地址 . . . . . . . . . . . . : 192.168.43.216 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 192.168.43.1 隧道适配器 本地连接* 3: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 dns 后缀 . . . . . . . : <<<
client:
stdout: 驱动器 c 中的卷是 系统 卷的序列号是 85c0-669a c:\users\administrator\pycharmprojects\internet_program 的目录 stderr:找不到文件 stdout: 驱动器 c 中的卷是 系统 卷的序列号是 85c0-669a c:\users\administrator\pycharmprojects\internet_program 的目录 2019/09/16 14:43 <dir> . 2019/09/16 14:43 <dir> .. 2019/09/16 14:37 <dir> .idea 2019/09/16 14:43 553 client1.py 2019/09/16 13:42 0 client2.py 2019/09/16 14:43 306 sever1.py 2019/09/16 01:41 70 time_test.py 2019/09/14 23:51 <dir> venv 4 个文件 929 字节 4 个目录 45,855,449,088 可用字节 stderr: stdout: windows ip 配置 以太网适配器 bluetooth 网络连接 2: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 dns 后缀 . . . . . . . : 以太网适配器 本地连接: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 dns 后缀 . . . . . . . : 无线局域网适配器 无线网络连接: 连接特定的 dns 后缀 . . . . . . . : 本地链接 ipv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6:8018%14 ipv4 地址 . . . . . . . . . . . . : 192.168.43.216 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 192.168.43.1 隧道适配器 本地连接* 3: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 dns 后缀 . . . . . . . : stderr: stdout: stderr:'pwd' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 stdout: stderr:'ip' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
可以看出udp不会有黏包现象,会产生丢包现象,没发完就不发了,不完整也不可靠。
黏包成因
tcp协议的数据传送
拆包机制
当发送端缓冲区的长度大于网卡的mtu时,tcp会将这次发送的数据拆成几个数据包发送出去。mtu是maximum transmission unit的缩写,意思是网络上传送最大数据包,mtu是字节单位,大部分网络设备的mtu都是1500.如果本机的mtu比网关的mtu大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。
在正常情况下它的拆包可理解为:
面向流的通信特点和nagle算法
tcp(transport control protocol,传输控制协议),是面向连接的,面向流的,提供高可靠性的服务。收发两端(客户端和服务端)都要有一一成对的socket,因此发送端为了将多个发往接收端的包,更有效地发往对方,使用了优化算法(nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样接收端就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。
对于空消息:tcp是基于数据流的,于是收发消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp协议是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息然后发出去。
可靠黏包的tcp协议:tcp协议数据不会丢,没有收完包,就会下次接收,会继续上次继续接受。
基于tcp协议特点的黏包现象成因
当我们在socket服务端发送值1、2,然后根据优化算法,它会把1先放到这个缓存当中等一等,然后再把2一起封装起来,然后再发出去,因此我们看到的就是黏包现象
这种现象的表面现象是两个send太近且发送的消息太短
发送端可以使1k1k地发送数据,而接收端的应用程序可以两k两k地提走数据,当然也有可能一次提走3k或6k数据,或者一次只提走几个字节的数据。也就是说,应用程序所看到的数据是一个整体,或者说是一个流(stream),一条消息有多少字节对应程序是不可见的,因此tcp协议是面向流的协议,这也是容易出现黏包问题的原因。而udp协议是面向消息的协议,每个udp段都是一条消息,应用程序必须以消息单位提取数据,不能一次提取任意字节的数据,这一点和tcp是很不同的。
解决黏包的方法
...
下一篇: PHP+Ajax点击加载更多列表数据实例