Python 在本地机器上 UDP socket 超时

1 投票
2 回答
8668 浏览
提问于 2025-04-17 16:27

我正在做一个基本的“ping”应用程序,使用UDP来完成一个作业,除了socket.settimeout()的实现之外,其他都正常。我搞不清楚为什么,但这和绑定的socket有关。这可能只是Python的一个小问题,但我想在记录之前确认一下。我并不是在寻找一个能正常运行的代码答案(那样就作弊了),而是想知道我现在的代码为什么会出问题。(比如:一些没有文档说明的原因,比如Python不喜欢在同一台机器上进行客户端/服务器的操作等等)

关于Python Socket超时的详细信息:http://docs.python.org/2/library/socket.html#socket.socket.settimeout

在下面的代码中,与在同一台机器上运行的服务器的通信是成功的,但前提是客户端没有绑定到socket上。如果客户端不绑定socket,超时系统就会失效(这是通过关闭服务器来测试的,这时所有十个超时会立即一次性打印出来)。

注意:代码并不理想,但这是一个网络理论课程,而不是编程课程。最终只要能工作就行。我现在就可以交上去拿个A,但我想理解为什么超时功能不工作。

编辑:为了澄清一个问题,客户端使用Bind是因为看到服务器代码中有这个,但我在意识到UDP不需要它之前就用了这个,结果恰好让超时功能正常工作,但却破坏了正常操作。难道socket.settimeout()的声明只适用于TCP吗?

客户端代码(包含超时):

import socket
import time
import select


data = "Re-verify our range to target... one ping only. \n"

addrDest = ("127.0.0.1",8675)
addrLocal = ("127.0.0.1",12345)

totalTime = 0
averageTime = 0
totalPings = 0
#timeout_seconds = 1.0

UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
UDPSock.bind(addrLocal) 
# adding bind here even though it's UDP makes timeout work,
# but breaks normal functionality
UDPSock.settimeout(1)


while (totalPings < 10):
 totalPings = (totalPings + 1)
 start = time.clock()
 str_list = []
 str_list.append(str(totalPings))
 str_list.append(" ")
 str_list.append(str(start))
 str_list.append(" ")
 str_list.append(data)
 dataOut = ''.join(str_list)
 UDPSock.sendto(dataOut,addrDest)
 try:
      dataIn,addrIn = UDPSock.recvfrom(1024)
      print dataIn.strip(),"\n",addrIn
      elapsed = ((time.clock() - start) * 1000)
      print elapsed, " ms round trip"
 except socket.error:
      print "Connection timed out for Packet #", totalPings

服务器代码:

import socket

UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

# (to all IP addresses on this system)
listen_addr = ("",8675)
UDPSock.bind(listen_addr)

# Report on all data packets received and
# where they came from in each case (as this is
# UDP, each may be from a different source and it's
# up to the server to sort this out!)
while True:
    data,addr = UDPSock.recvfrom(1024)
    print data.strip(),addr
    UDPSock.sendto(data,addr)

2 个回答

0

我通过去掉异常处理找到了问题的原因。当服务器关闭时,会出现一个套接字错误,具体来说是“socket.error: [Errno 10054] 远程主机强制关闭了一个现有连接”,这是因为它试图从套接字读取数据。显然,当套接字在Python中没有绑定时,它会忽略超时功能(这就是为什么我绑定时超时能正常工作)。

如果我运行服务器,但不发送任何数据(通过注释掉最后一行),当程序没有收到数据包时,它会正确超时。我还打算使用一个更具体的异常处理器。最后,这只是Python的一个小问题,以及UDP是无连接的事实。

另外,有人提到可以用“select”来解决这个问题。我查了一下,结果我会得到一个if...else的语句块,这种方法有点用,但还是更喜欢使用原生的异常处理。谢谢大家。

-Jimmy

2

为什么客户端需要绑定到本地地址呢?客户端会在某个时候变成服务器吗?如果不会,那就根本不需要绑定客户端。只有当你希望客户端充当服务器时,才需要指定一个端口。如果你不调用绑定,它会随机生成一个端口号,范围是(0 - 1023是保留的)1024 - 65535(如果我没记错的话),这个端口号就是UDP数据包中的源端口,而源地址就是客户端运行的地址。

根据伯克利套接字的说法:

bind() assigns a socket to an address. When a socket is created using socket(), 
it is only given a protocol family, but not assigned an address. This association with an address must be performed with the bind() system call before the socket can accept connections to other hosts

如果这是一个网络课程项目,而你正在尝试实现客户端-服务器架构,那么你绝对不应该在客户端代码中调用bind,因为客户端不应该充当服务器,客户端应该连接到一个正在监听的服务器,而不是服务器去连接客户端。

更新:

在TCP客户端-服务器设计中可能需要调用绑定,但在UDP客户端-服务器模型中则不需要,因为UDP是一种“发送后就忘”的设计,没有低级的数据包发送成功确认。UDP数据包内部会包含源地址和端口。

撰写回答