我们如何确定套接字是否已准备好读/写?



我们如何确定套接字是否准备好在套接字编程中读/写

在 Linux 上,使用 select() 或 poll()。

在 Windows 上,您可以使用 winsock2 中的 WSAPoll() 或 select()。

Mac OS X 也有 select() 和 poll()。

#include

intselect(int nfds, fd_set *readFDS, fd_set *writeFDS, fd_set *例外,结构时间值*超时);

select() 和 pselect() 允许程序监视多个文件描述符,等待一个或多个文件描述符"准备好"用于某些级别的 I/O 操作(例如,可能的输入)。如果文件描述符可以执行相应的 I/O 操作 (例如, read(2)) 而不阻塞,则认为文件描述符已准备就绪。– https://linux.die.net/man/3/fd_set

#include

int poll(struct pollfd *FDS, nfds_t NFDS, int timeout);

poll() 执行的任务与 select(2) 类似:它等待一组文件描述符中的一个准备好执行 I/O。 – https://linux.die.net/man/2/poll

选择用法示例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int
main(void)
{
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don't rely on the value of tv now! */
if (retval == -1)
perror("select()");
else if (retval)
printf("Data is available now.n");
/* FD_ISSET(0, &rfds) will be true. */
else
printf("No data within five seconds.n");
exit(EXIT_SUCCESS);
}

上述代码的说明:

FD_ZERO初始化rfds集。FD_SET(0, &rfds)将 fd 0 (stdin) 添加到集合中。FD_ISSET可用于在选择返回后检查特定文件描述符是否已准备就绪。

此示例中的选择调用会等到 rfds 输入或 5 秒过去。select 调用中的两个NULL分别是要检查文件描述符集 (fd_sets) 以检查准备写入状态和异常的位置。tv参数是等待的秒数和微秒数。要选择的第一个参数nfds是三个集合(读取、写入、异常集)中的任何一个中编号最高的文件描述符加 1。

轮询用法示例(从 man7.org 开始):

/* poll_input.c
Licensed under GNU General Public License v2 or later.
*/
#include <poll.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); 
} while (0)
int
main(int argc, char *argv[])
{
int nfds, num_open_fds;
struct pollfd *pfds;
if (argc < 2) {
fprintf(stderr, "Usage: %s file...n", argv[0]);
exit(EXIT_FAILURE);
}
num_open_fds = nfds = argc - 1;
pfds = calloc(nfds, sizeof(struct pollfd));
if (pfds == NULL)
errExit("malloc");
/* Open each file on command line, and add it 'pfds' array. */
for (int j = 0; j < nfds; j++) {
pfds[j].fd = open(argv[j + 1], O_RDONLY);
if (pfds[j].fd == -1)
errExit("open");
printf("Opened "%s" on fd %dn", argv[j + 1], pfds[j].fd);
pfds[j].events = POLLIN;
}
/* Keep calling poll() as long as at least one file descriptor is
open. */
while (num_open_fds > 0) {
int ready;
printf("About to poll()n");
ready = poll(pfds, nfds, -1);
if (ready == -1)
errExit("poll");
printf("Ready: %dn", ready);
/* Deal with array returned by poll(). */
for (int j = 0; j < nfds; j++) {
char buf[10];
if (pfds[j].revents != 0) {
printf("  fd=%d; events: %s%s%sn", pfds[j].fd,
(pfds[j].revents & POLLIN)  ? "POLLIN "  : "",
(pfds[j].revents & POLLHUP) ? "POLLHUP " : "",
(pfds[j].revents & POLLERR) ? "POLLERR " : "");
if (pfds[j].revents & POLLIN) {
ssize_t s = read(pfds[j].fd, buf, sizeof(buf));
if (s == -1)
errExit("read");
printf("    read %zd bytes: %.*sn",
s, (int) s, buf);
} else {                /* POLLERR | POLLHUP */
printf("    closing fd %dn", pfds[j].fd);
if (close(pfds[j].fd) == -1)
errExit("close");
num_open_fds--;
}
}
}
}
printf("All file descriptors closed; byen");
exit(EXIT_SUCCESS);
}

以上代码说明:

此代码比前面的示例稍微复杂一些。

argc是参数的数量。argv是提供给程序的参数数组。argc[0] 通常是程序的名称。如果 argc 小于 2(这意味着只给出了一个参数),程序将输出一条使用消息,并以失败代码退出。

pfds = calloc(nfds, sizeof(struct pollfd));struct pollfd数组分配内存,该数组nfds元素长,并将内存清零。然后有一个空检查;如果 pfds 为 NULL,则表示 calloc 失败(通常是因为程序内存不足),因此程序会打印错误并退出。

for 循环打开argv中指定的每个文件名,并将其分配给 pfd 数组的相应元素。然后设置每个元素.eventsPOLLIN告诉轮询以检查每个文件描述符是否准备好读取

while 循环是对 poll() 的实际调用发生的地方。结构 pollfds 数组、pfds、fds 的数量、nfds和超时 -1 被传递给轮询。然后检查返回值是否存在错误(-1 是出现错误时轮询返回的值),如果出现错误,程序将打印错误消息并退出。然后打印准备好的文件描述符的数量。

在 while 循环内的第二个 for 循环中,程序遍历pollfd数组并检查每个结构的.revents字段。如果该字段不为零,则相应的文件描述符上发生了事件。程序打印文件描述符和事件,可以是POLLIN(准备输入)、POLLHUP(挂断)或POLLERR(错误条件)。如果事件是 POLLIN,则文件已准备好读取。

然后程序将 10 个字节读入buf。如果在读取时发生错误,程序将打印错误并退出。否则,程序将打印读取的字节数和缓冲区的内容buf

如果出现错误或挂断(POLLERRPOLLHUP),程序会关闭文件描述符并递减num_open_fds

最后,程序说所有文件描述符都已关闭并以EXIT_SUCCESS退出。

最新更新