UNIX 非阻塞 I/O: O_NONBLOCK vs. FIONBIO

在我遇到的 BSD 套接字编程上下文中的每个示例和讨论中,似乎将文件描述符设置为非阻塞 I/O 模式的推荐方法是使用 O_NONBLOCK标志来设置 fcntl(),例如。

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

我在 UNIX 中从事网络编程已经超过十年了,并且一直使用 FIONBIO ioctl()调用来完成这项工作:

int opt = 1;
ioctl(fd, FIONBIO, &opt);

我从来没想过为什么,就是这么学的。

有人对这两者各自可能的优点作出评论吗?我想可移植性轨迹有所不同,但不知道在多大程度上 ioctl_list(2)不能代表单个 ioctl方法的那个方面。

124933 次浏览

I believe fcntl() is a POSIX function. Where as ioctl() is a standard UNIX thing. Here is a list of POSIX io. ioctl() is a very kernel/driver/OS specific thing, but i am sure what you use works on most flavors of Unix. some other ioctl() stuff might only work on certain OS or even certain revs of it's kernel.

As @Sean said, fcntl() is largely standardized, and therefore available across platforms. The ioctl() function predates fcntl() in Unix, but is not standardized at all. That the ioctl() worked for you across all the platforms of relevance to you is fortunate, but not guaranteed. In particular, the names used for the second argument are arcane and not reliable across platforms. Indeed, they are often unique to the particular device driver that the file descriptor references. (The ioctl() calls used for a bit-mapped graphics device running on an ICL Perq running PNX (Perq Unix) of twenty years ago never translated to anything else anywhere else, for example.)

Prior to standardization there was ioctl(...FIONBIO...) and fcntl(...O_NDELAY...), but these behaved inconsistently between systems, and even within the same system. For example, it was common for FIONBIO to work on sockets and O_NDELAY to work on ttys, with a lot of inconsistency for things like pipes, fifos, and devices. And if you didn't know what kind of file descriptor you had, you'd have to set both to be sure. But in addition, a non-blocking read with no data available was also indicated inconsistently; depending on the OS and the type of file descriptor the read may return 0, or -1 with errno EAGAIN, or -1 with errno EWOULDBLOCK. Even today, setting FIONBIO or O_NDELAY on Solaris causes a read with no data to return 0 on a tty or pipe, or -1 with errno EAGAIN on a socket. However 0 is ambiguous since it is also returned for EOF.

POSIX addressed this with the introduction of O_NONBLOCK, which has standardized behavior across different systems and file descriptor types. Because existing systems usually want to avoid any changes to behavior which might break backward compatibility, POSIX defined a new flag rather than mandating specific behavior for one of the others. Some systems like Linux treat all 3 the same, and also define EAGAIN and EWOULDBLOCK to the same value, but systems wishing to maintain some other legacy behavior for backward compatibility can do so when the older mechanisms are used.

New programs should use fcntl(...O_NONBLOCK...), as standardized by POSIX.