第一部分: 简介 tcp socket 通信的底层原理
原理解析图:
socket 通信过程如图所示: 首先客户端将发送内容通过 send() 方法将内容发送到客户端计算机的内核区, 然后由操作系统将内容通过底层路径发送到服务器端的内核区, 然后由服务器程序通过 recv() 方法从服务器端计算机内核区取出数据.
因此我们可以了解到, send 方法并不是直接将内容发送到服务器端, recv 方法也并不是直接将从客户端发来的内容接收到服务器程序内存中, 而是操作自己机器的内核区.
第二部分: 产生粘包的原因 (只针对 tcp)
产生粘包的情况有两种:
1: 当连续发送数据时, 由于 tcp 协议的 nagle 算法, 会将较小的内容拼接成大的内容, 一次性发送到服务器端, 因此造成粘包
2: 当发送内容较大时, 由于服务器端的 recv(buffer_size) 方法中的 buffer_size 较小, 不能一次性完全接收全部内容, 因此在下一次请求到达时, 接收的内容依然是上一次没有完全接收完的内容, 因此造成粘包现象.
也就是说: 接收方不知道该接收多大的数据才算接收完毕, 造成粘包.
第三部分: 如何解决上述两种粘包现象?
思路一: 对于第一种粘包产生方式可以在两次 send() 直接使用 recv() 来阻止连续发送的情况发生. 代码就不用展示了.
思路二: 由于产生粘包的原因是接收方的无边界接收, 因此发送端可以在发送数据之前向接收端告知发送内容的大小即可. 代码示例如下:
方式一: 分两次通讯分别传递内容大小和内容
服务器端代码:
- # __author__:Kelvin
- # date:2019/4/28 21:36
- from socket import *
- import subprocess
- server = socket(AF_INET, SOCK_STREAM)
- server.bind(("127.0.0.1", 8000))
- server.listen(5)
- while True:
- conn, addr = server.accept()
- print("创建了一个新的连接!")
- while True:
- try:
- data = conn.recv(1024)
- if not data: break
- res = subprocess.Popen(data.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
- stderr=subprocess.PIPE)
- err = res.stderr.read()
- if err:
- cmd_msg = err
- else:
- cmd_msg = res.stdout.read()
- if not cmd_msg: cmd_msg = "action success!".encode("gbk")
- length = len(cmd_msg)
- conn.send(str(length).encode("utf-8"))
- conn.recv(1024)
- conn.send(cmd_msg)
- except Exception as e:
- print(e)
- break
客户端代码:
- # __author__:Kelvin
- # date:2019/4/28 21:36
- from socket import *
- client = socket(AF_INET, SOCK_STREAM)
- client.connect(("127.0.0.1", 8000))
- while True:
- inp = input(">>:")
- if not inp: continue
- if inp == "quit": break
- client.send(inp.encode("utf-8"))
- length = int(client.recv(1024).decode("utf-8"))
- client.send("ready!".encode("utf-8"))
- lengthed = 0
- cmd_msg = b""
- while lengthed <length:
- cmd_msg += client.recv(1024)
- lengthed = len(cmd_msg)
- print(cmd_msg.decode("gbk"))
方式二: 一次通讯直接传递内容大小和内容
服务器端:
- # __author__:Kelvin
- # date:2019/4/28 21:36
- from socket import *
- import subprocess
- import struct
- server = socket(AF_INET, SOCK_STREAM)
- server.bind(("127.0.0.1", 8000))
- server.listen(5)
- while True:
- conn, addr = server.accept()
- print("创建了一个新的连接!")
- while True:
- try:
- data = conn.recv(1024)
- if not data: break
- res = subprocess.Popen(data.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
- stderr=subprocess.PIPE)
- err = res.stderr.read()
- if err:
- cmd_msg = err
- else:
- cmd_msg = res.stdout.read()
- if not cmd_msg: cmd_msg = "action success!".encode("gbk")
- length = len(cmd_msg)
- conn.send(struct.pack("i", length))
- conn.send(cmd_msg)
- except Exception as e:
- print(e)
- break
客户端:
- # __author__:Kelvin
- # date:2019/4/28 21:36
- from socket import *
- import struct
- client = socket(AF_INET, SOCK_STREAM)
- client.connect(("127.0.0.1", 8000))
- while True:
- inp = input(">>:")
- if not inp: continue
- if inp == "quit": break
- client.send(inp.encode("utf-8"))
- length = struct.unpack("i",client.recv(4))[0]
- lengthed = 0
- cmd_msg = b""
- while lengthed < length:
- cmd_msg += client.recv(1024)
- lengthed = len(cmd_msg)
- print(cmd_msg.decode("gbk"))
上述两种方式均可以解决粘包问题.
来源: https://www.cnblogs.com/sun-10387834/p/10790999.html