【发布时间】:2010-10-14 15:16:12
【问题描述】:
我希望我的 python 应用程序能够判断另一侧的套接字何时被删除。有这个方法吗?
【问题讨论】:
-
您在阅读(并获得 EOF)吗?或者你在写(并得到 I/O 错误)?还是只是等待使用 select?
我希望我的 python 应用程序能够判断另一侧的套接字何时被删除。有这个方法吗?
【问题讨论】:
如果我没记错的话,这通常是通过timeout 处理的。
【讨论】:
来自 Jweede 发布的链接:
socket.timeout 异常:
This exception is raised when a timeout occurs on a socket which has had timeouts enabled via a prior call to settimeout(). The accompanying value is a string whose value is currently always “timed out”.
这里是来自python docs的socket模块的演示服务器和客户端程序
# Echo server program
import socket
HOST = '' # Symbolic name meaning all available interfaces
PORT = 50007 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
data = conn.recv(1024)
if not data: break
conn.send(data)
conn.close()
还有客户:
# Echo client program
import socket
HOST = 'daring.cwi.nl' # The remote host
PORT = 50007 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)
在我从中提取这些的文档示例页面上,有更复杂的示例采用了这个想法,但这里是简单的答案:
假设您正在编写客户端程序,只需将所有使用套接字的代码(当它有被丢弃的风险)放在一个 try 块中......
try:
s.connect((HOST, PORT))
s.send("Hello, World!")
...
except socket.timeout:
# whatever you need to do when the connection is dropped
【讨论】:
send() 不会触发超时。只有connect() 和recv() 这样做。
这取决于您所说的“丢弃”是什么意思。对于 TCP 套接字,如果另一端通过以下方式关闭连接 close() 或进程终止,您将通过读取文件结尾或收到读取错误来发现,通常将 errno 设置为操作系统设置的任何“对等连接重置”。对于 python,你将读取一个长度为零的字符串,或者当你尝试从套接字读取或写入时会抛出一个 socket.error。
【讨论】:
简答:
使用非阻塞recv(),或阻塞recv() / select() 短暂的超时。
长答案:
处理套接字连接的方法是根据需要读取或写入,并准备处理连接错误。
TCP 区分“断开”连接的 3 种形式:超时、重置、关闭。
其中,超时并不能真正检测到,TCP 可能只会告诉你时间还没有到期。但即使它告诉你,时间也可能马上就到期了。
还请记住,您或您的对等方(连接的另一端)使用 shutdown() 可能只关闭传入字节流,并保持传出字节流运行,或者关闭传出流并保持传入字节流运行.
所以严格来说,你要检查读取流是否关闭,或者写入流是否关闭,或者两者都关闭。
即使连接被“断开”,您仍然应该能够读取仍在网络缓冲区中的任何数据。只有在缓冲区为空后,您才会收到与 recv() 的断开连接。
检查连接是否断开就像问“读取当前缓冲的所有数据后我会收到什么?”要找出这一点,您只需读取当前缓冲的所有数据。
我可以看到“读取所有缓冲的数据”对于一些人来说可能是个问题,他们仍然认为 recv() 是一个阻塞函数。使用阻塞的 recv(),当缓冲区已经为空时“检查”读取将阻塞,这违背了“检查”的目的。
在我看来,任何被记录为可能无限期阻塞整个过程的函数都是一个设计缺陷,但我想它仍然存在由于历史原因,从当使用套接字时就像一个常规文件描述符是一个很酷的想法。
你可以做的是:
对于问题的写入部分,保持读取缓冲区为空几乎可以解决问题。在非阻塞读取尝试后,您会发现连接“断开”,并且您可以选择在读取返回关闭的通道后停止发送任何内容。
我想确保您发送的数据已到达另一端(并且不在发送缓冲区中)的唯一方法是:
python socket howto 说如果通道关闭,send() 将返回 0 个字节写入。您可以使用非阻塞或超时 socket.send() 如果它返回 0,则您不能再在该套接字上发送数据。但是如果它返回非零,你已经发送了一些东西,祝你好运:)
另外,在这里我没有考虑将 OOB(带外)套接字数据作为解决您的问题的一种方法,但我认为 OOB 不是您的意思。
【讨论】:
我将这篇博文中的代码示例翻译成 Python:How to detect when the client closes the connection?,对我来说效果很好:
from ctypes import (
CDLL, c_int, POINTER, Structure, c_void_p, c_size_t,
c_short, c_ssize_t, c_char, ARRAY
)
__all__ = 'is_remote_alive',
class pollfd(Structure):
_fields_ = (
('fd', c_int),
('events', c_short),
('revents', c_short),
)
MSG_DONTWAIT = 0x40
MSG_PEEK = 0x02
EPOLLIN = 0x001
EPOLLPRI = 0x002
EPOLLRDNORM = 0x040
libc = CDLL('libc.so.6')
recv = libc.recv
recv.restype = c_ssize_t
recv.argtypes = c_int, c_void_p, c_size_t, c_int
poll = libc.poll
poll.restype = c_int
poll.argtypes = POINTER(pollfd), c_int, c_int
class IsRemoteAlive: # not needed, only for debugging
def __init__(self, alive, msg):
self.alive = alive
self.msg = msg
def __str__(self):
return self.msg
def __repr__(self):
return 'IsRemoteAlive(%r,%r)' % (self.alive, self.msg)
def __bool__(self):
return self.alive
def is_remote_alive(fd):
fileno = getattr(fd, 'fileno', None)
if fileno is not None:
if hasattr(fileno, '__call__'):
fd = fileno()
else:
fd = fileno
p = pollfd(fd=fd, events=EPOLLIN|EPOLLPRI|EPOLLRDNORM, revents=0)
result = poll(p, 1, 0)
if not result:
return IsRemoteAlive(True, 'empty')
buf = ARRAY(c_char, 1)()
result = recv(fd, buf, len(buf), MSG_DONTWAIT|MSG_PEEK)
if result > 0:
return IsRemoteAlive(True, 'readable')
elif result == 0:
return IsRemoteAlive(False, 'closed')
else:
return IsRemoteAlive(False, 'errored')
【讨论】:
CDLL('libc.so.6')。我不知道它在 Windows 的 Linux 子系统中是否相同。抱歉,我知道我没有安装 Windows 来测试它。