select() + 非阻塞 write() 是否可以在阻塞管道或套接字上使用?

mel*_*sgl 5 unix sockets posix pipe blocking

情况是我有一个阻塞管道或套接字 fd,我不想write()阻塞,所以我先做了select()一个,但这仍然不能保证write()不会阻塞。

这是我收集的数据。即使select()指示可以写入,写入的PIPE_BUF字节数也会阻塞。然而,写入最多PIPE_BUF字节在实践中似乎不会阻塞,但POSIX 规范并未强制要求。

那只指定原子行为。Python(!) 文档指出:

报告为准备写入的文件select()poll()或此模块中的类似接口保证不会在写入最多PIPE_BUF字节时阻塞。POSIX 保证该值至少为 512.

在下面的测试程序中,设置BUF_BYTES100000write()成功选择后在 Linux、FreeBSD 或 Solaris 上阻塞 。我假设命名管道与匿名管道具有相似的行为。

不幸的是,阻塞套接字也会发生同样的情况。通话 test_socket()main(),并使用一个相当大的BUF_BYTES100000好这里太)。目前尚不清楚是否有像PIPE_BUF套接字一样的安全缓冲区大小 。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <limits.h>
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>

#define BUF_BYTES PIPE_BUF
char buf[BUF_BYTES];

int
probe_with_select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds)
{
    struct timeval timeout = {0, 0};
    int n_found = select(nfds, readfds, writefds, exceptfds, &timeout);
    if (n_found == -1) {
        perror("select");
    }
    return n_found;
}

void
check_if_readable(int fd)
{
    fd_set fdset;
    FD_ZERO(&fdset);
    FD_SET(fd, &fdset);
    printf("select() for read on fd %d returned %d\n",
           fd, probe_with_select(fd + 1, &fdset, 0, 0));
}

void
check_if_writable(int fd)
{
    fd_set fdset;
    FD_ZERO(&fdset);
    FD_SET(fd, &fdset);
    int n_found = probe_with_select(fd + 1, 0, &fdset, 0);
    printf("select() for write on fd %d returned %d\n", fd, n_found);
    /* if (n_found == 0) { */
    /*     printf("sleeping\n"); */
    /*     sleep(2); */
    /*     int n_found = probe_with_select(fd + 1, 0, &fdset, 0); */
    /*     printf("retried select() for write on fd %d returned %d\n",  */
    /*            fd, n_found); */
    /* } */
}

void
test_pipe(void)
{
    int pipe_fds[2];
    size_t written;
    int i;
    if (pipe(pipe_fds)) {
        perror("pipe failed");
        _exit(1);
    }
    printf("read side pipe fd: %d\n", pipe_fds[0]);
    printf("write side pipe fd: %d\n", pipe_fds[1]);
    for (i = 0; ; i++) {
        printf("i = %d\n", i);
        check_if_readable(pipe_fds[0]);
        check_if_writable(pipe_fds[1]);
        written = write(pipe_fds[1], buf, BUF_BYTES);
        if (written == -1) {
            perror("write");
            _exit(-1);
        }
        printf("written %d bytes\n", written);
    }
}

void
serve()
{
    int listenfd = 0, connfd = 0;
    struct sockaddr_in serv_addr;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, '0', sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(5000);

    bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    listen(listenfd, 10);

    connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);

    sleep(10);
}

int
connect_to_server()
{
    int sockfd = 0, n = 0;
    struct sockaddr_in serv_addr;

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        exit(-1);
    }

    memset(&serv_addr, '0', sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000);

    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("inet_pton");
        exit(-1);
    }

    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connect");
        exit(-1);
    }

    return sockfd;
}

void
test_socket(void)
{
    if (fork() == 0)  {
        serve();
    } else {
        int fd;
        int i;
        int written;
        sleep(1);
        fd = connect_to_server();

        for (i = 0; ; i++) {
            printf("i = %d\n", i);
            check_if_readable(fd);
            check_if_writable(fd);
            written = write(fd, buf, BUF_BYTES);
            if (written == -1) {
                perror("write");
                _exit(-1);
            }
            printf("written %d bytes\n", written);
        }
    }
}

int
main(void)
{
    test_pipe();
    /* test_socket(); */
}
Run Code Online (Sandbox Code Playgroud)

cko*_*vas 2

除非您希望在 select() 表示 fd 已准备好写入时一次发送一个字节,否则实际上无法知道您将能够发送多少字节,即使如此,理论上也是可能的(至少在文档中) ,如果不是在现实世界中)让 select 表示它已准备好写入,然后条件在 select() 和 write() 之间的时间内发生变化。

非阻塞发送是这里的解决方案,如果您从使用 write() 更改为 send(),则无需将文件描述符更改为非阻塞模式即可以非阻塞形式发送一条消息。您需要更改的唯一一件事是将 MSG_DONTWAIT 标志添加到发送调用中,这将使发送非阻塞,而无需更改套接字的属性。在这种情况下,您甚至根本不需要使用 select(),因为 send() 调用将为您提供返回码中所需的所有信息 - 如果您得到的返回码为 -1 并且 errno 为 EAGAIN或 EWOULDBLOCK 那么您就知道无法再发送了。