Python 接收 UDP 数据时的套接字错误 (10054)

3 投票
3 回答
16076 浏览
提问于 2025-04-15 21:14

我现在在使用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 个回答

1

嗯,这看起来很明显。

  1. UDP没有连接,所以 Client.connect 这个写法是错的。
  2. 你把客户端的地址存储在 Server.connecteddict 里。当客户端关闭时,就没有人能接收到你发送的内容了。

搞定网络通信其实很难,因为 socket 库太底层了(它只是C语言套接字的一个简单封装)。你的代码里缺少了很多细节。我建议你试试更高级的库,比如 twisted。这里有一些 关于UDP的例子,可以帮助你入门。

2

这个问题其实比看起来简单多了。用socket.recv()代替socket.recvfrom()就可以了。我在本地做了这个修改,你的代码就能正常运行了。

9

首先,这可能和你使用的平台有关,但你没有提到你在什么平台上运行;不过,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的网络代码可能会将所有这样的失败返回转换成异常。

撰写回答