如果在超时之前没有收到任何数据,那么 Python 的 socket.recv() 对于非阻塞套接字将返回什么?
- 2025-03-14 08:57:00
- admin 原创
- 52
问题描述:
基本上,我在几个地方读到过它socket.recv()
会返回任何可以读取的内容,或者一个空字符串,表示另一端已关闭(官方文档甚至没有提到当连接关闭时它会返回什么……太棒了!)。这对于阻塞套接字来说都很好,因为我们知道recv()
只有当实际有东西要接收时才会返回,所以当它返回一个空字符串时,它一定意味着另一端已经关闭了连接,对吧?
好的,很好,但是当我的套接字是非阻塞的时会发生什么?我搜索了一下(可能还不够,谁知道呢?),但还是搞不清楚如何判断对方何时使用非阻塞套接字关闭了连接。似乎没有方法或属性可以告诉我们这一点,将返回值recv()
与空字符串进行比较似乎完全没用……只有我有这个问题吗?
举一个简单的例子,假设我的套接字超时设置为 1.2342342(这里任意一个非负数)秒,然后我调用socket.recv(1024)
,但另一端在 1.2342342 秒内没有发送任何内容。调用recv()
将返回一个空字符串,我不知道连接是否仍然有效...
解决方案 1:
对于非阻塞套接字,若没有可用数据,recv 将抛出 socket.error 异常,异常值的 errno 为 EAGAIN 或 EWOULDBLOCK。例如:
import sys
import socket
import fcntl, os
import errno
from time import sleep
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
fcntl.fcntl(s, fcntl.F_SETFL, os.O_NONBLOCK)
while True:
try:
msg = s.recv(4096)
except socket.error, e:
err = e.args[0]
if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
sleep(1)
print 'No data available'
continue
else:
# a "real" error occurred
print e
sys.exit(1)
else:
# got a message, do something :)
socket.settimeout(n)
如果您通过或 的超时启用了非阻塞行为,情况会有所不同socket.setblocking(False)
。在这种情况下,仍会引发 socket.error,但在超时的情况下,异常的伴随值始终是设置为“超时”的字符串。因此,要处理这种情况,您可以执行以下操作:
import sys
import socket
from time import sleep
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
s.settimeout(2)
while True:
try:
msg = s.recv(4096)
except socket.timeout, e:
err = e.args[0]
# this next if/else is a bit redundant, but illustrates how the
# timeout exception is setup
if err == 'timed out':
sleep(1)
print 'recv timed out, retry later'
continue
else:
print e
sys.exit(1)
except socket.error, e:
# Something else happened, handle error, exit, etc.
print e
sys.exit(1)
else:
if len(msg) == 0:
print 'orderly shutdown on server end'
sys.exit(0)
else:
# got a message do something :)
正如评论所指出的,这也是一个更可移植的解决方案,因为它不依赖于操作系统特定的功能来将套接字置于非阻塞模式。
有关更多详细信息,请参阅recv(2)和python socket。
解决方案 2:
很简单:如果recv()
返回 0 字节;您将不会再通过此连接接收任何数据。永远。您仍可以发送。
这意味着如果没有可用数据但连接仍然处于活动状态(另一端可能发送),则非阻塞套接字必须引发异常(它可能与系统有关)。
解决方案 3:
当您使用recv
连接时select
,如果套接字已准备好读取但没有可读取的数据,则意味着客户端已关闭连接。
下面是处理此问题的一些代码,还请注意在 while 循环中第二次调用时抛出的异常recv
。如果没有剩余内容可读,则会抛出此异常,但这并不意味着客户端已关闭连接:
def listenToSockets(self):
while True:
changed_sockets = self.currentSockets
ready_to_read, ready_to_write, in_error = select.select(changed_sockets, [], [], 0.1)
for s in ready_to_read:
if s == self.serverSocket:
self.acceptNewConnection(s)
else:
self.readDataFromSocket(s)
接收数据的函数:
def readDataFromSocket(self, socket):
data = ''
buffer = ''
try:
while True:
data = socket.recv(4096)
if not data:
break
buffer += data
except error, (errorCode,message):
# error 10035 is no data available, it is non-fatal
if errorCode != 10035:
print 'socket.error - ('+str(errorCode)+') ' + message
if data:
print 'received '+ buffer
else:
print 'disconnected'
解决方案 4:
为了完成现有的答案,我建议使用 select 而不是非阻塞套接字。关键是非阻塞套接字使事情变得复杂(也许除了发送),所以我认为根本没有理由使用它们。如果您经常遇到应用程序被阻塞等待 IO 的问题,我还会考虑在后台的单独线程中执行 IO。
扫码咨询,免费领取项目管理大礼包!