Python 接收 UDP 数据时的套接字错误 (10054)
我现在在使用UDP和Python的socket模块时遇到了一个问题。我们有一个服务器和一些客户端。当我们向某个用户发送数据时,可能会出现问题。这个用户可能因为客户端崩溃、网络服务提供商断开连接,或者其他一些不正常的方式关闭了与服务器的连接。因此,有可能会向一个已经关闭的socket发送数据。
当然,使用UDP时,你无法知道数据是否真的到达了目的地,或者连接是否已经关闭,因为它对此并不在意(至少,它不会抛出异常)。不过,如果你发送数据时连接已经关闭,你会以某种方式收到数据(???),这会导致在sock.recvfrom时出现socket错误。[Errno 10054] 远程主机强制关闭了一个现有的连接。这几乎就像是连接的自动响应。
虽然这没什么大问题,可以通过try: except:块来处理(即使这会稍微降低服务器的性能)。但问题是,我无法知道这个错误是从哪个用户发来的,或者哪个socket已经关闭。有没有办法找出是谁(IP地址,socket编号)发送了这个错误?如果能知道就太好了,这样我可以立即断开他们的连接,并将他们从数据中移除。有什么建议吗?谢谢。
服务器:
import socket
class Server(object):
def __init__(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.connected = {}
def connect(self):
self.socket.bind(('127.0.0.1', 5579))
def find_data(self):
while 1:
data, address = self.socket.recvfrom(1024)
self.got_data(data,address)
if self.connected.has_key(address):
pass
else:
self.connected[address] = None
def got_data(self, data, address):
print "GOT",data,"FROM",address
for people in self.connected:
print people
self.send_data('hi', people)
def send_data(self, data, address):
self.socket.sendto(data,address)
if __name__ == '__main__':
server = Server()
server.connect()
print "NOW SEARCHING FOR DATA"
server.find_data()
客户端:
import socket, time
class Client(object):
def __init__(self):
self.socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
def connect(self):
self.socket.connect(('127.0.0.1', 5579))
def send_data(self):
self.socket.sendto('hi',('127.0.0.1', 5579))
def got_data(self, data, address):
print "GOT",data,"FROM",address
if __name__ == '__main__':
client = Client()
client.connect()
while 1:
client.send_data()
time.sleep(5)
3 个回答
这个问题其实比看起来简单多了。用socket.recv()代替socket.recvfrom()就可以了。我在本地做了这个修改,你的代码就能正常运行了。
首先,这可能和你使用的平台有关,但你没有提到你在什么平台上运行;不过,10054是WSAECONNRESET
,所以我猜你可能是在某种Windows平台上。
其次,正如之前提到的,这和UDP没有关系。你在客户端调用Connect()
只是让你的客户端网络代码允许你使用Send()
来发送数据,而不是SendTo()
。而且,当你使用Send()
发送数据时,它会默认使用你在Connect()
中提供的地址。
第三,我很惊讶你得到的是WSAECONNRESET
而不是ERROR_PORT_UNREACHABLE
;不过,底层原因可能是一样的。远程机器上的UDP协议栈如果没有在你发送的端口上打开的socket,可能会发送一个ICMP端口不可达的错误。所以,如果你的客户端发送了数据然后关闭了socket,而你的服务器又向客户端地址发送数据,你就会收到端口不可达的错误,而某些版本的Windows可能会把这个错误翻译成连接重置错误……
这些ICMP端口不可达错误的问题在于,它们是通过Winsock代码在一个待处理的UDP接收(Recv/RecvFrom)调用失败时报告的。正如我在这里和问题这里中解释的,UDP协议栈显然知道产生端口不可达的地址,但它不会把这个信息传递给调用者,所以你无法将这些消息映射到有用的信息上。可能你使用的是Vista之前的Windows版本,UDP协议栈对地址做了一些有用的处理,并且确实将错误报告给了正确的socket,但也不要太指望。
最后,无论如何你都有一个问题;ICMP端口不可达错误并不是可靠传递的,所以你无法确定如果你尝试向一个已经不在的客户端发送UDP数据时是否会收到错误。在我看来,这意味着即使有时有效,你也不应该依赖它。
显然,你试图在UDP上构建某种面向连接的协议(否则你的服务器为什么要保留客户端的地址)。你需要做更多的工作来创建一个可行的伪连接,而首先要意识到的就是,唯一知道客户端是否已经离开的方法是设置你自己的超时,如果在一定时间内没有收到他们的消息,就“断开”你的伪连接。
当然,这并没有回答你的问题;你如何避免异常。我想你可能无法做到。异常的原因可能是Recv()
或RecvFrom()
调用返回了一个“失败”的返回码,而Python的网络代码可能会将所有这样的失败返回转换成异常。