使用非阻塞UDP读取时缺失消息
我在使用非阻塞读取UDP消息时遇到了丢失消息的问题,这个问题发生在两个主机之间。发送方在Linux上,接收方在Windows XP上。下面的Python示例展示了这个问题。
这里有三个脚本用来展示这个问题。
send.py:
import socket, sys
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
host = sys.argv[1]
s.sendto('A'*10, (host,8888))
s.sendto('B'*9000, (host,8888))
s.sendto('C'*9000, (host,8888))
s.sendto('D'*10, (host,8888))
s.sendto('E'*9000, (host,8888))
s.sendto('F'*9000, (host,8888))
s.sendto('G'*10, (host,8888))
read.py
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('',8888))
while True:
data,address = s.recvfrom(10000)
print "recv:", data[0],"times",len(data)
read_nb.py
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('',8888))
s.setblocking(0)
data =''
address = ''
while True:
try:
data,address = s.recvfrom(10000)
except socket.error:
pass
else:
print "recv:", data[0],"times",len(data)
示例1(正常工作):
ubuntu> python send.py
winxp > read.py
从read.py得到的结果是:
接收到:A 次 10
接收到:B 次 9000
接收到:C 次 9000
接收到:D 次 10
接收到:E 次 9000
接收到:F 次 9000
接收到:G 次 10
示例2(丢失消息):
在这种情况下,短消息经常会被read_nb.py漏掉。我给出两个例子来说明这个问题。
ubuntu> python send.py
winxp > read_nb.py
从read_nb.py得到的结果是:
接收到:A 次 10
接收到:B 次 9000
接收到:C 次 9000
接收到:D 次 10
接收到:E 次 9000
接收到:F 次 9000
上面是最后一个10字节的消息丢失了
下面是中间的一个10字节的消息丢失了
接收到:A 次 10
接收到:B 次 9000
接收到:C 次 9000
接收到:E 次 9000
接收到:F 次 9000
接收到:G 次 10
我用Wireshark在Windows上检查过,每次所有消息都被捕获,所以它们确实到达了主机接口,但没有被read_nb.py捕获。这是什么原因呢?
我还尝试过在Linux上使用read_nb.py,在Windows上使用send.py,这样就能正常工作。
所以我觉得这个问题可能和winsock2有关。
或者我可能是错误地使用了非阻塞UDP?
2 个回答
如果数据包已经到达主机(就像你在wireshark日志中看到的那样),那么我首先会检查一下你的socket接收缓冲区的大小,尽量把它调大,然后尽可能快地运行。
当然,这在使用UDP时是完全正常的。你应该明白,数据包可能会在任何时候、出于任何原因被丢弃。此外,你可能会收到重复的数据包……
如果你需要可靠性,那你就得自己去实现,或者使用TCP协议。
使用UDP时丢失消息是很正常的,因为它的传输层不保证数据包的顺序或送达。如果你需要保证消息的顺序或者一定要送达,那就换成TCP,或者自己实现一些机制,比如排序、确认、超时和重传。
关于你的例子,较大的消息超过了正常以太网的最大传输单元(MTU),这个值是1500字节,减去8字节的UDP头部(除非你使用的是超大帧)。因此,发送方会把这些消息分成小块发送。这会增加发送方和接收方的负担,但接收方的负担更重,因为它需要在内存中保存这些小块,直到完整的数据包到达。
我不太相信你会因为36030字节而导致接收缓冲区溢出,但我自己从来不在Windows上做网络编程,所以你最好按照@Len的建议检查一下接收方的SO_RECVBUF
这个套接字选项的值。
另外,检查一下netstat -s
的输出,看看丢包的数量。