Python--网络编程-----粘包的底层原理分析
一、send是不是直接把数据发给服务端
不是,要想发数据,必须得通过网卡发送数据,应用软件是无法直接通过网卡发送数据的,它需要调用操作系统接口,
也就是说,应用软件把要发送的数据由应用系统内存copy到操作系统内存,进而由操作系统控制数据的发送,copy到
操作系统内存也意味着send已经发送完毕了,它是无法控制操作系统怎样发送数据的。
二、recv是不是直接从客户端接收数据
不是,与send数据相同,应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,应用软件把要接收的数
据由操作系统内存copy到应用系统内存。
三、不管是recv还是send都不是直接接收对方的数据,而是操作自己操作系统内存--------》不是一个send对应一个recv
1、recv:
wait data:耗时非常长
copy data
2、send:
copy data
3、socket 为提高传输效率,发送方往往要收集到足够多的数据后才发送一次数据给对方。若连续几次需要send的数据都很少,
通常TCP socket 会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
4、发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,
合并成一个大的数据块,然后进行封包。
5、所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
四、粘包不一定会发生,在数据量小,时间间隔短的情况下会发生,代码示例如下:
1、客户端粘包
1 客户端代码: 2 import socket 3 4 5 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 6 7 client.connect(('127.0.0.1', 9903)) 8 9 client.send('hello'.encode('utf-8')) 10 client.send('world'.encode('utf-8')) 11 12 服务端代码: 13 import socket 14 15 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 16 server.bind(('127.0.0.1', 9903)) # 0-65535:0-1024给操作系统使用 17 server.listen(5) 18 19 conn, addr = server.accept() 20 21 res1 = conn.recv(1024) 22 print('第一次结果:', res1) 23 24 res2 = conn.recv(1024) 25 print('第二次结果:', res2) 26 27 启动之后服务端结果为: 28 第一次结果: b'helloworld' 29 第二次结果: b''
两次send在客户端已经粘包,一次性发给服务端,所以服务端第一次就从服务端的操作系统内存中取出了两次send内容
如果在客户端两次send中间加一个time.sleep(1),服务端结果为:
1 第一次结果: b'hello' 2 第二次结果: b'world'
可以看出,客户端不会等待1秒钟,而直接把hello发过去了,1秒钟后,再把world发送到服务端,这样客户端和服务端都没有发生粘包
2、服务端粘包
1 服务端代码为: 2 3 import socket 4 5 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 6 server.bind(('127.0.0.1', 9903)) # 0-65535:0-1024给操作系统使用 7 server.listen(5) 8 9 conn, addr = server.accept() 10 11 res1 = conn.recv(1) 12 print('第一次结果:', res1) 13 14 res2 = conn.recv(1024) 15 print('第二次结果:', res2) 16 17 客户端代码为: 18 19 import socket 20 21 22 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 23 24 client.connect(('127.0.0.1', 9903)) 25 26 client.send('hello'.encode('utf-8')) 27 client.send('world'.encode('utf-8')) 28 29 服务端运行结果为: 30 31 第一次结果: b'h' 32 第二次结果: b'elloworld'
第一次recv只接收了一个byte的数据h,ello四个字节的数据遗留在管道中,会和后面的数据在服务端发生粘包现象,
如果知道发送端的数据量大小,第一次recv5个byte的数据,第二次接收5个byte的数据,也不会发生粘包现象,
或者服务端代码改为如下:
1 import socket 2 3 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 4 server.bind(('127.0.0.1', 9903)) # 0-65535:0-1024给操作系统使用 5 server.listen(5) 6 7 conn, addr = server.accept() 8 9 res1 = conn.recv(1) 10 res2 = conn.recv(1) 11 res3 = conn.recv(1) 12 res4 = conn.recv(1) 13 res5 = conn.recv(1) 14 print('第一次结果:', res1+res2+res3+res4+res5) 15 16 res2 = conn.recv(1024) 17 print('第二次结果:', res2)
服务端也不会发生粘包现象,因为保证了第一次结果把第一次send的数据接收完整,
所以说,要想避免粘包现象,必须知道发送端send的数据量的大小,然后服务端根据send的数据量的大小recv数据,