在Windows上使用Glib监视套接字使其处于非阻塞模式
以下代码在Windows上运行不正常(但在Linux上可以正常工作):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(True)
sock.connect(address)
gobject.io_add_watch(
sock.fileno(),
gobject.IO_OUT | gobject.IO_ERR | gobject.IO_HUP,
callback)
在glib源代码的不同地方,以及其他地方,有评论提到在Windows上,套接字在轮询时会被设置为非阻塞模式。结果就是回调函数self.outgoing_cb
会不断被调用,而写入套接字时会出现这个错误信息:
[Errno 10035] A non-blocking socket operation could not be completed immediately
在写入之前调用sock.setblocking(True)
似乎并不能解决这个问题。通过降低轮询的优先级,并忽略错误信息,虽然可以按预期工作,但会产生太多事件,并消耗大量CPU资源。有没有办法绕过Windows的这个限制呢?
更新
我想指出,轮询POLLOUT
的主要目的是在你进行写入调用时不会收到EAGAIN
/EWOULDBLOCK
的错误信息。我认为我遇到的奇怪错误信息是Windows对应这两个错误代码的表现。换句话说,当套接字不允许我成功写入时,我收到了gobject.IO_OUT
事件,而且将其设置为阻塞模式仍然给我这个不合适的错误。
另一个更新
在Linux上,这个功能正常工作,套接字并没有切换到非阻塞模式,当套接字允许我写入而不阻塞或抛出错误时,我会收到IO_OUT
。我想在Windows上尽量模拟/恢复这种功能。
进一步说明
来自man poll
:
poll() performs a similar task to select(2): it waits for one of a set
of file descriptors to become ready to perform I/O.
POLLOUT
Writing now will not block.
来自man select
:
A file descriptor is considered ready if it is possible to perform the corre‐
sponding I/O operation (e.g., read(2)) without blocking.
4 个回答
我不确定这是否有帮助(我对轮询函数和MFC套接字不太熟悉,也不知道轮询是否是你程序结构的要求),所以请谨慎参考:
为了避免在写入时出现阻塞或EAGAIN错误,我们使用了select函数,也就是把套接字添加到传给select的写入集合中。如果select()返回的结果是rc=0,说明这个套接字可以立即进行写入……
我们在应用中使用的写入循环是(伪代码):
set_nonblocking.
count= 0.
do {
FDSET writefds;
add skt to writefds.
call select with writefds and a reaonsable timeout.
if (select fails with timeout) {
die with some error;
}
howmany= send(skt, buf+count, total-count).
if (howmany>0) {
count+= howmany.
}
} while (howmany>0 && count<total);
GIO 包含了 GSocket,这是一个“低级网络套接字对象”,自2.22版本开始就有了。不过,这个功能还没有移植到 Windows上的pygobject。
使用非阻塞输入输出(I/O)会有什么问题吗?如果你在使用阻塞I/O,使用轮询循环似乎有点奇怪。
当我写这样的程序时,我通常会这样做:
先把想要发送到文件描述符的字节存起来。
只有在这个存储区不空的时候,才请求
IO_OUT
(或者poll()
的等价物POLLOUT
)事件。当
poll()
(或类似的函数)通知你可以写的时候,就开始写。如果你收到EAGAIN
/EWOULDBLOCK
的错误,就把成功写入的字节从存储区移除,等下次再被通知。如果你成功写完了整个存储区的内容,就停止请求POLLOUT
,这样就不会无缘无故被唤醒。
(我猜Win32的绑定可能使用了WSAEventSelect和WaitForMultipleObjects()
来模拟poll()
,但结果是一样的……)
我不太确定你想用阻塞套接字的方式是怎么工作的。你一直在“被唤醒”,因为你请求在可以写的时候唤醒你。你只想在有数据要写的时候才指定这个……但是,当它唤醒你时,系统并不会告诉你可以写多少数据而不阻塞,所以这就是使用非阻塞I/O的一个好理由。