在Windows上使用Glib监视套接字使其处于非阻塞模式

3 投票
4 回答
2185 浏览
提问于 2025-04-15 15:31

以下代码在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 个回答

0

我不确定这是否有帮助(我对轮询函数和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);
1

GIO 包含了 GSocket,这是一个“低级网络套接字对象”,自2.22版本开始就有了。不过,这个功能还没有移植到 Windows上的pygobject

1

使用非阻塞输入输出(I/O)会有什么问题吗?如果你在使用阻塞I/O,使用轮询循环似乎有点奇怪。

当我写这样的程序时,我通常会这样做:

  • 先把想要发送到文件描述符的字节存起来。

  • 只有在这个存储区不空的时候,才请求IO_OUT(或者poll()的等价物POLLOUT)事件。

  • poll()(或类似的函数)通知你可以写的时候,就开始写。如果你收到EAGAIN/EWOULDBLOCK的错误,就把成功写入的字节从存储区移除,等下次再被通知。如果你成功写完了整个存储区的内容,就停止请求POLLOUT,这样就不会无缘无故被唤醒。

(我猜Win32的绑定可能使用了WSAEventSelectWaitForMultipleObjects()来模拟poll(),但结果是一样的……)

我不太确定你想用阻塞套接字的方式是怎么工作的。你一直在“被唤醒”,因为你请求在可以写的时候唤醒你。你只想在有数据要写的时候才指定这个……但是,当它唤醒你时,系统并不会告诉你可以写多少数据而不阻塞,所以这就是使用非阻塞I/O的一个好理由。

撰写回答