我们可以制作一个带有阻塞套接字的非阻塞服务器吗?

Can we make a non-blocking server with blocking sockets?

我必须为我的 IT 学校制作一个简单的 IRC client/server 程序。主题要求我们使用 select(2) 进行套接字轮询,但禁止我们使用 O_NONBLOCK 套接字。

  • Your server will accept multiple simultaneous connections.
    Attention, the use of fork is prohibited. So you should imperatively use select
  • Your server must not be blocking.
    This has nothing to do with non-blocking sockets, which are prohibited (so do not use fcntl(s, O_NONBLOCK))

我想知道是否有可能甚至使用 select(2).

设计一个带有阻塞套接字的非阻塞服务器(不分叉)

这是一个简单的例子:假设我们有一个简单的文本协议,每行一个命令。每个客户端都有一个缓冲区。当 select(2) 告诉我们客户端已准备好接收 read(2) 时,我们会一直读取直到在客户端缓冲区中找到 \n,因此我们会处理命令。使用非阻塞套接字,我们会读到 EAGAIN.

现在想象一下,我们正在使用阻塞套接字,恶意客户端发送没有换行符的文本。 select(2) 告诉我们数据可用,然后我们在客户端上 read(2)。但是我们永远不会读到预期的\n。系统调用将无限期阻塞,而不是返回 EAGAIN。这是拒绝服务攻击。

是否真的可以设计一个带有阻塞套接字和select(2)(没有fork(2))的非阻塞服务器?

是的,您从套接字中读到一次 select 告诉您已准备就绪。如果 read 包含 \n,则处理该行。否则,存储接收到的任何数据,并立即返回到 select

这当然意味着,对于每个打开的套接字,您必须维护状态信息,以及到目前为止读取的数据缓冲区。这允许代码独立处理每个 read,而无需在返回 select.

之前完成整行

这不可能。

  1. select() 块,因此调用它的任何程序也是如此。
  2. Posix为send()在阻塞模式下定义的行为是它阻塞,直到所有提供的数据都已传输到套接字发送缓冲区。除非你要深入研究低水位线等,否则不可能提前知道套接字发送缓冲区中是否有足够的空间让任何给定的 send() 不阻塞地完成,因此不可能任何调用 send() 的程序都不会阻塞。

    请注意,select() 对此没有帮助。它可以告诉你什么时候有一些房间,但不能告诉你什么时候有足够的房间。