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

2022-01-22 00:00:00 python sockets client udp

问题描述

我目前在使用 UDP 和 Python 套接字模块时遇到问题.我们有服务器和客户端.当我们向用户发送数据时,就会出现问题.用户可能通过客户端崩溃、ISP 断开连接或其他一些不正确的方法关闭了与服务器的连接.因此,可以将数据发送到关闭的套接字.

I currently have a problem using UDP and Python socket module. We have a server and clients. The problem occurs when we send data to a user. It's possible that user may have closed their connection to the server through a client crash, disconnect by ISP, or some other improper method. As such, it is possible to send data to a closed socket.

当然,对于 UDP,您无法判断数据是否真的到达或是否已关闭,因为它不在乎(至少,它不会引发异常).但是,如果您发送数据并且它被关闭,您会以某种方式获取数据(???),这最终会在 sock.recvfrom 上给您一个套接字错误.[Errno 10054] 现有连接被远程主机强行关闭.几乎看起来像是来自连接的自动响应.

Of course with UDP you can't tell if the data really reached or if it's closed, as it doesn't care (atleast, it doesn't bring up an exception). However, if you send data and it is closed off, you get data back somehow (???), which ends up giving you a socket error on sock.recvfrom. [Errno 10054] An existing connection was forcibly closed by the remote host. Almost seems like an automatic response from the connection.

虽然这很好,并且可以通过 try: except: block 来处理(即使它会稍微降低服务器的性能).问题是,我不知道这是来自谁或关闭了哪个套接字.无论如何要找出'谁'(ip,socket#)发送了这个?太好了,因为我可以立即断开它们并将它们从数据中删除.有什么建议?谢谢.

Although this is fine, and can be handled by a try: except: block (even if it lowers performance of the server a little bit). The problem is, I can't tell who this is coming from or what socket is closed. Is there anyway to find out 'who' (ip, socket #) sent this? It would be great as I could instantly just disconnect them and remove them from the data. Any suggestions? Thanks.

服务器:

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)


解决方案

首先,这可能是特定于平台的,您没有提及您正在运行的平台;但是,10054 是 WSAECONNRESET 所以我猜是某种 Windows 平台.

Firstly this is possibly platform specific and you don't mention the platform that you're running on; however, 10054 is WSAECONNRESET so I'm guessing a Windows platform of some kind.

其次,如前所述,与 UDP 没有连接.您在客户端中对 Connect() 的调用只会导致客户端计算机上的网络代码允许您发起 Send() 调用,而不是 SendTo() 调用并简单地将发送数据的地址默认为在您发出 Send() 调用时提供给对 Connect() 调用的地址.

Secondly as previously pointed out there is no connection with UDP. Your call to Connect() in the client simply causes the networking code on your client machine to allow you to initiate Send() calls rather than SendTo() calls and simply default the address that you are sending data to when you issue Send() calls to the address supplied to the call to Connect().

第三,我很惊讶你得到的是 WSAECONNRESET 而不是 ERROR_PORT_UNREACHABLE;但是根本原因可能是相同的.如果您要发送到的端口上没有打开套接字,远程计算机上的 UDP 堆栈可能会发送 ICMP Port Unreachable 错误.因此,如果您的客户端发送数据然后关闭套接字,然后您的服务器将数据发送回客户端地址,您将获得一个无法访问的端口,并且某些版本的 Windows 可能会将其转换为连接重置错误...

Thirdly I'm surprised that you're getting WSAECONNRESET and not ERROR_PORT_UNREACHABLE; however the underlying reason is probably the same. The UDP stack on the remote machine will likely be sending a ICMP Port Unreachable error if there's no socket open on the port that you are sending to. So, if your client sends data and then closes the socket and then your server sends data back to the client address you'll get a port unreachable and some versions of windows might be translating that into a connection reset error...

这些 ICMP 端口不可达错误的问题在于,它们是通过 Winsock 代码报告的,因为挂起的 UDP Recv/RecvFrom 调用失败.正如我解释的那样这里 和问题 here UDP 堆栈显然知道生成端口无法访问的地址,但它不会将该信息传递给调用者,因此您无法将这些消息映射到有用的东西.您可能在 Vista 之前的 Windows 版本上运行,并且 UDP 堆栈对该地址执行了一些有用的操作,并且它正在向正确的套接字报告错误,但不要赌它.

The problem with these ICMP port unreachable errors, is that they're reported via the Winsock code by failing a pending UDP Recv/RecvFrom call. As I explain here and question here the UDP stack obviously knows the address that generated the port unreachable but it doesn't pass that information on to the caller so there's nothing that you can do to map these messages to something useful. It's possible that you're running on a version of Windows prior to Vista and the UDP stack is doing something useful with the address and it IS reporting the error to the correct socket, but don't bet on it.

最后你还是有问题;ICMP 端口不可达错误未可靠传递,因此如果您尝试将 UDP 数据发送到已消失的客户端,您无法确定是否会收到错误.恕我直言,这意味着即使它有时有效,您也不应该依赖它.

Finally you have a problem anyway; the ICMP port unreachable error isn't delivered reliably so you can't know for sure that you WILL get an error if you try and send UDP data to a client that has gone away. IMHO this means that you shouldn't rely on it even if it works sometimes.

您显然是在尝试在 UDP 之上构建某种面向连接的协议(否则为什么您的服务器会保留客户端的地址).您必须做更多的工作才能通过 UDP 创建一个可行的伪连接,首先要意识到的是,您知道客户端何时离开的唯一方法是设置您自己的超时并断开"您的如果您在一段时间内没有收到他们的消息,则为伪连接.

You are obviously attempting to build some kind of connection oriented protocol on top of UDP (why else would your server hold on to the addresses of the clients). You'll have to do a lot more to create a viable pseudo connection over UDP and one of the first things to realise is that the only way you can know when a client has gone away is to set your own timeout and 'disconnect' your pseudo connection if you don't hear from them within a certain period of time.

这当然不能回答你的问题;你如何避免异常.我希望你不能.异常的原因可能是来自 Recv()RecvFrom() 调用的失败"返回码,python 网络代码可能会将所有此类失败返回转换为你的例外.

Of course this doesn't answer your question; how do you avoid the exception. I expect you can't. The cause of the exception is likely a 'failure' return code from a Recv() or RecvFrom() call and the python network code is probably converting all such failure returns into exceptions for you.

相关文章