• 第二十八天- tcp下的粘包和解决方案


    1.什么是粘包

      写在前面:只有TCP有粘包现象,UDP永远不会粘包

      1.TCP下的粘包

      因为TCP协议是面向连接、面向流的,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,这就导致了数据量小的粘包现象;同时因为tcp的协议的安全可靠性,在没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会导致数据量大的粘包;

      2.UDP没粘包原因:

      UDP是无连接的,面向消息的,为提供高效率服务 ,并不会使用块的合并优化算法;同时由于UDP支持一对多的模式,所以接收端缓冲区采用了链式结构来记录每一个到达的UDP包,也就是在每个UDP包中有头(消息来源地址,端口等信息),这样,对于接收端来说就是有边界的,所以UDP永远没粘包。 

    2.两种粘包情况  

      1.情况一 发送方的缓存机制
      发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,优化机制会合到一起,产生粘包)

     1 # 连续传小包被优化机制合并导致的粘包
     2 
     3 import socket
     4 
     5 server = socket.socket()
     6 ip_port = ('127.0.0.1',8081)
     7 server.bind(ip_port)
     8 server.listen()
     9 conn,addr = server.accept()
    10 
    11 from_client_msg1 = conn.recv(1024).decode('utf-8')
    12 from_client_msg2 = conn.recv(1024).decode('utf-8')
    13 
    14 print(from_client_msg1)
    15 print(from_client_msg2)
    16 # heheheenenen  两条消息被优化合并在了一块
    17 
    18 conn.close()
    19 server.close()
    View 小数据粘包_server Code
     1 import socket
     2 
     3 client = socket.socket()
     4 ip_port = ('127.0.0.1',8081)
     5 client.connect(ip_port)
     6 
     7 To_server_msg1 = client.send(b'hehehe')
     8 To_server_msg2 = client.send(b'enenen')
     9 
    10 client.close()
    View 小数据粘包_client Code

      2.情况二 接收方的缓存机制

      接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

     1 # 传输数据超出接收范围导致的粘包
     2 import socket
     3 import subprocess
     4 
     5 server = socket.socket()
     6 ip_port = ('127.0.0.1',8001)
     7 server.bind(ip_port)
     8 server.listen()
     9 conn,addr = server.accept()
    10 
    11 while 1:
    12     from_client_cmd = conn.recv(1024).decode('utf-8')
    13 
    14     sub_obj = subprocess.Popen(
    15         from_client_cmd,
    16         shell=True,
    17         stdout=subprocess.PIPE,
    18         stderr=subprocess.PIPE,
    19     )
    20     cmd_res = sub_obj.stdout.read()
    21     print('结果长度>>>', len(cmd_res))
    22     conn.send(cmd_res)  # 发内容给客户端
    数据大粘包_server
     1 import socket
     2 
     3 client = socket.socket()
     4 ip_port = ('127.0.0.1',8001)
     5 client.connect(ip_port)
     6 
     7 
     8 while 1:
     9     client_cmd = input('请输入系统指令>>>')
    10     client.send(client_cmd.encode('utf-8'))
    11 
    12     from_server_msg = client.recv(1024)  # 接收返回的消息
    13 
    14     print(from_server_msg.decode('gbk'))
    15     # 可以看到没一次性接收完毕,执行下条命令出现了上次的结果,这就是数据大超出接收范围的粘包。
    数据大粘包_client

      3.总结

      黏包现象只发生在tcp协议中:

      1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。

      2.实际上,主要还是因为接收方不知道消息间的界限,不知道一次性提取多少字节的数据所造成。

    3.粘包解决方案

      1.解决方案一:
      问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

     1 import socket
     2 import subprocess
     3 
     4 server = socket.socket()
     5 ip_port = ('127.0.0.1',8001)
     6 server.bind(ip_port)
     7 server.listen()
     8 conn,addr = server.accept()
     9 
    10 while 1:
    11     from_client_cmd = conn.recv(1024).decode('utf-8')  # 接收传来的命令
    12     sub_obj = subprocess.Popen(
    13         from_client_cmd,
    14         shell=True,
    15         stdout=subprocess.PIPE,
    16         stderr=subprocess.PIPE,
    17     )
    18     # subprocess对象.read 得到命令结果,是bytes类型的
    19     str_byt = sub_obj.stdout.read()
    20     str_len = len(str_byt)
    21     print(str_len)
    22     conn.send(str(str_len).encode('utf-8'))  # 先发长度
    23 
    24     from_client_msg = conn.recv(1024).decode('utf-8')
    25     if from_client_msg == 'ok':
    26         conn.send(str_byt)  # 客户端确认收到长度后 再发送真实内容
    27     else:
    28         print("客户端未收到长度!")
    29         break
    30 
    31 conn.close()
    32 server.close()
    解决小包粘包_服务端
     1 import socket
     2 
     3 client = socket.socket()
     4 ip_port = ('127.0.0.1',8001)
     5 client.connect(ip_port)
     6 
     7 
     8 while 1:
     9     client_cmd = input('请输入系统指令>>>')
    10     client.send(client_cmd.encode('utf-8'))
    11 
    12     from_server_len = client.recv(1024).decode('utf-8')  # 接收返回的长度
    13     print(from_server_len)
    14     client.send(b'ok')
    15 
    16     from_server_msg = client.recv(int(from_server_len))  # 注意还原成int
    17     print(from_server_msg.decode('gbk'))
    解决小包粘包_客户端

      不足之处:程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗

      2.解决方案进阶:
      可借助struct模块,这个模块可把要发的数据长度转成固定长度字节。这样客户端每次接收消息前只要先收到这个固定长度字节的内容,收到接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能完整接收的数据了。

      struct模块:该模块可以把一个类型,如数字,转成固定长度的bytes

    1 import struct
    2 
    3 res = struct.pack('i',1111111)  # i 模式  1111111 要转换的内容
    4 print(res)  # b'Gxf4x10x00'

      模式,见下图:

     

     1 import socket
     2 import struct
     3 import subprocess
     4 
     5 server = socket.socket()
     6 ip_port = ('127.0.0.1',8001)
     7 server.bind(ip_port)
     8 server.listen()
     9 conn,addr = server.accept()
    10 
    11 while 1:
    12     from_client_cmd = conn.recv(1024).decode("utf-8") # 注意转码
    13     # print(from_client_cmd)
    14     sub_obj = subprocess.Popen(
    15         from_client_cmd,
    16         shell=True,
    17         stdout=subprocess.PIPE,
    18         stderr=subprocess.PIPE,
    19     )
    20     cmd_res = sub_obj.stdout.read()  # 得到bytes类型所有内容
    21     str_len = len(cmd_res)
    22     print(str_len)
    23 
    24     str_len1 = struct.pack('i',str_len)  # 把长度打包成4字节的bytes
    25 
    26     conn.send(str_len1 + cmd_res)  # 拼接字节 把长度和内容打包发给客户端
    struct方案解决粘包_server
     1 import socket, struct
     2 
     3 client = socket.socket()
     4 ip_port = ('127.0.0.1',8001)
     5 client.connect(ip_port)
     6 
     7 while 1:
     8     str_cmd = input('请输入命令>>> ').encode('utf-8')
     9     client.send(str_cmd)
    10     # 先接收4个字节,4个字节是数据的真实长度转换而成的
    11     str_len = client.recv(4)
    12     # print(str_len)
    13     num = struct.unpack('i',str_len)[0]  # 注意解出来是一个元组 用下标把真实长度取出来
    14 
    15     print(num)
    16 
    17     str_res = client.recv(num)
    18     print(str_res.decode('gbk'))
    struct方案解决粘包_client

    补充:获取缓冲区大小

    1 # 获取socket缓冲区大小
    2 import socket
    3 from socket import SOL_SOCKET,SO_REUSEADDR,SO_SNDBUF,SO_RCVBUF
    4 sk = socket.socket(type=socket.SOCK_DGRAM)
    5 # sk.setsockopt(SOL_SOCKET,SO_RCVBUF,80*1024)
    6 sk.bind(('127.0.0.1',8090))
    7 print('>>>>', (sk.getsockopt(SOL_SOCKET, SO_SNDBUF))/1024)
    8 print('>>>>', sk.getsockopt(SOL_SOCKET, SO_RCVBUF))
  • 相关阅读:
    MTK相关汇总
    Android TV 学习笔记
    进程限制_limit
    Linux TV 学习笔记
    Linux firmware子系统的实现机制学习笔记
    C++ STL deque
    C++ STL Vector
    C++ STL几种数据结构对比
    vector系列--creating vector of local structure、vector of structs initialization
    vector系列--vector<unique_ptr<>>赋值给vector<unique_ptr<>>
  • 原文地址:https://www.cnblogs.com/xi1419/p/10010200.html
Copyright © 2020-2023  润新知