zoukankan      html  css  js  c++  java
  • 浅谈tcp粘包问题

    第一部分:简介tcp socket通信的底层原理

    原理解析图:

    1 socket通信过程如图所示:首先客户端将发送内容通过send()方法将内容发送到客户端计算机的内核区,然后由操作系统将内容通过底层路径发送到服务器端的内核区,然后由服务器程序通过recv()方法从服务器端计算机内核区取出数据。
    2 因此我们可以了解到,send方法并不是直接将内容发送到服务器端,recv方法也并不是直接将从客户端发来的内容接收到服务器程序内存中,而是操作自己机器的内核区。

    第二部分:产生粘包的原因(只针对tcp)

    产生粘包的情况有两种:

    1 1:当连续发送数据时,由于tcp协议的nagle算法,会将较小的内容拼接成大的内容,一次性发送到服务器端,因此造成粘包
    2 
    3 2:当发送内容较大时,由于服务器端的recv(buffer_size)方法中的buffer_size较小,不能一次性完全接收全部内容,因此在下一次请求到达时,接收的内容依然是上一次没有完全接收完的内容,因此造成粘包现象。

    也就是说:接收方不知道该接收多大的数据才算接收完毕,造成粘包。

    第三部分:如何解决上述两种粘包现象?

    思路一:对于第一种粘包产生方式可以在两次send()直接使用recv()来阻止连续发送的情况发生。代码就不用展示了。

    思路二:由于产生粘包的原因是接收方的无边界接收,因此发送端可以在发送数据之前向接收端告知发送内容的大小即可。代码示例如下:

      方式一:分两次通讯分别传递内容大小和内容

      服务器端代码:

     1 # __author__:Kelvin
     2 # date:2019/4/28 21:36
     3 from socket import *
     4 import subprocess
     5 
     6 server = socket(AF_INET, SOCK_STREAM)
     7 server.bind(("127.0.0.1", 8000))
     8 server.listen(5)
     9 
    10 while True:
    11     conn, addr = server.accept()
    12     print("创建了一个新的连接!")
    13     while True:
    14         try:
    15             data = conn.recv(1024)
    16             if not data: break
    17             res = subprocess.Popen(data.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
    18                                    stderr=subprocess.PIPE)
    19             err = res.stderr.read()
    20             if err:
    21                 cmd_msg = err
    22             else:
    23                 cmd_msg = res.stdout.read()
    24             if not cmd_msg: cmd_msg = "action success!".encode("gbk")
    25             length = len(cmd_msg)
    26             conn.send(str(length).encode("utf-8"))
    27             conn.recv(1024)
    28             conn.send(cmd_msg)
    29         except Exception as e:
    30             print(e)
    31             break

      客户端代码:

     1 # __author__:Kelvin
     2 # date:2019/4/28 21:36
     3 from socket import *
     4 
     5 client = socket(AF_INET, SOCK_STREAM)
     6 client.connect(("127.0.0.1", 8000))
     7 while True:
     8     inp = input(">>:")
     9     if not inp: continue
    10     if inp == "quit": break
    11     client.send(inp.encode("utf-8"))
    12     length = int(client.recv(1024).decode("utf-8"))
    13     client.send("ready!".encode("utf-8"))
    14     lengthed = 0
    15     cmd_msg = b""
    16     while lengthed < length:
    17         cmd_msg += client.recv(1024)
    18         lengthed = len(cmd_msg)
    19     print(cmd_msg.decode("gbk"))

      方式二:一次通讯直接传递内容大小和内容

      服务器端:

     1 # __author__:Kelvin
     2 # date:2019/4/28 21:36
     3 from socket import *
     4 import subprocess
     5 import struct
     6 
     7 server = socket(AF_INET, SOCK_STREAM)
     8 server.bind(("127.0.0.1", 8000))
     9 server.listen(5)
    10 
    11 while True:
    12     conn, addr = server.accept()
    13     print("创建了一个新的连接!")
    14     while True:
    15         try:
    16             data = conn.recv(1024)
    17             if not data: break
    18             res = subprocess.Popen(data.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
    19                                    stderr=subprocess.PIPE)
    20             err = res.stderr.read()
    21             if err:
    22                 cmd_msg = err
    23             else:
    24                 cmd_msg = res.stdout.read()
    25             if not cmd_msg: cmd_msg = "action success!".encode("gbk")
    26             length = len(cmd_msg)
    27             conn.send(struct.pack("i", length))
    28             conn.send(cmd_msg)
    29         except Exception as e:
    30             print(e)
    31             break

      客户端:

     1 # __author__:Kelvin
     2 # date:2019/4/28 21:36
     3 from socket import *
     4 import struct
     5 
     6 client = socket(AF_INET, SOCK_STREAM)
     7 client.connect(("127.0.0.1", 8000))
     8 while True:
     9     inp = input(">>:")
    10     if not inp: continue
    11     if inp == "quit": break
    12     client.send(inp.encode("utf-8"))
    13     length = struct.unpack("i",client.recv(4))[0]
    14     lengthed = 0
    15     cmd_msg = b""
    16     while lengthed < length:
    17         cmd_msg += client.recv(1024)
    18         lengthed = len(cmd_msg)
    19     print(cmd_msg.decode("gbk"))

    上述两种方式均可以解决粘包问题。

  • 相关阅读:
    services parameters 是如何表现的
    session表有多少条记录?
    php://input 如何用?
    getEditableConfigNames
    UTC + 8 = Beijing Time
    file vs database
    多环境drupal安全install.php
    新建block+cache
    drupal 8 bigpipe lazy_builder
    hook_theme 的重要性
  • 原文地址:https://www.cnblogs.com/sun-10387834/p/10790999.html
Copyright © 2011-2022 走看看