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是很不同的。

 

解决黏包的方法

 

...

相关内容

    暂无相关文章

评论关闭