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

时间:2015-05-06 03:11:58

标签: sockets unix pipe posix blocking

情况是我有一个阻止管道或套接字fd,我想write()没有阻塞,所以我先做select(),但仍然没有#39; t保证write()不会阻止。

这是我收集的数据。即使select()表明这一点 写入是可能的,写入超过PIPE_BUF个字节可以阻止。 但是,写入最多PIPE_BUF个字节似乎无法阻止 练习,但POSIX spec没有规定。

仅指定原子行为。 Python(!) documentation声明:

  

报告的文件已准备好由select()poll()或类似内容撰写   这个模块中的接口保证不会在写入时阻塞   到PIPE_BUF个字节。 POSIX至少保证该值   512

在以下测试程序中,将BUF_BYTES设置为100000以阻止 成功选择后,在Linux,FreeBSD或Solaris上write()。一世 假设命名管道与匿名管道具有相似的行为。

不幸的是,阻塞套接字会发生同样的情况。呼叫 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(); */
}

3 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:1)

你引用的Posix部分明确指出:

  

[for pipes]如果O_NONBLOCK标志清零,写请求可能导致线程阻塞,但在正常完成时它将返回nbyte。

     

[对于流,可能包括流套接字]如果O_NONBLOCK清零,并且STREAM无法接受数据(由于内部流控制条件,STREAM写入队列已满),write()将阻塞,直到可以接受数据。

因此,您引用的Python文档仅适用于非阻止模式。但是,由于你没有使用Python,它无论如何都没有相关性。

答案 2 :(得分:0)

ckolivas的答案是正确的,但是阅读这篇文章后,我认为我可以出于兴趣考虑添加一些测试数据。

我迅速编写了一个读取速度慢的tcp服务器(读取之间睡眠100毫秒),每个周期读取4KB。然后是一个我用来测试各种写场景的快速写客户端。两者都在读取(服务器)或写入(客户端)之前使用select。

这是在运行Windows 7 VM(VirtualBox)的Linux Mint 18上分配的1GB内存。

对于阻止案例

如果可以写入“一定数量的字节”,请选择return,然后写入立即全部完成或阻塞直到完成。在我的系统上,此“一定字节数”至少为1MB。在OP的系统上,这显然要少得多(少于100,000)。

因此select不会返回 not ,直到可能写入至少1MB。从来没有(如果我看到的)情况,如果随后发生较小的写操作,则select将返回。因此,在该系统上,选择x表示4K或8K或128K的select + write(x)永远不会阻止写入。

这当然很好,但这是一个具有1GB内存的卸载VM。其他系统可能会有所不同。但是,我会期望写在选择之后发出的某个不可思议的数字(也许是PIPE_BUF)以下,永远不会在所有符合POSIX的系统上阻塞。但是(再次),我没有看到任何能达到这种效果的文档,因此不能依赖这种行为(即使Python文档显然可以做到这一点)。正如OP所说,目前尚不清楚套接字是否有像PIPE_BUF这样的安全缓冲区大小。真可惜。

即使我只争辩说只有一个字节可用时,理性的系统也不会从选择中返回,这是ckolivas的帖子所说的是什么!

其他信息:

在任何情况下(在正常操作中)写入都不会返回请求的全部金额(或错误)以外的任何内容。

如果服务器被杀死(ctrl-c),则客户端写操作将立即返回一个值(通常小于请求的值-没有正常操作!),并且没有其他错误指示​​。下一个选择调用将立即返回,随后的写操作将返回-1,并且errno表示“对等方重置连接”。那是人们期望的-这次写的尽可能多,下次再写。

此(和EINTR)似乎是写操作返回的唯一数字,该数字> 0但小于请求的数字。

如果服务器端正在读取并且客户端被杀死,则服务器将继续读取所有可用数据,直到用完为止。然后读取零并关闭套接字。

对于非阻塞情况:

某些不可思议的值以下的行为与上述相同。选择返回,写不会阻塞(当然),并且写全部完成。

我的问题是否则会发生什么。 send(2)手册页指出,在非阻塞模式下,发送失败并显示EAGAIN或EWOULDBLOCK。这可能暗示(取决于您的阅读方式)全部还是全部。除了它还说选择可以用来确定何时可以发送更多的数据。所以它不可能全部或全部都没有。

写入(与不带标志的发送相同),表示返回的内容可能少于请求的内容。这种挑剔似乎很古怪,但手册页是福音,所以我这样阅读。

在测试中,非阻塞写入的值大于某些特定值,返回的值小于请求的值。这个值不是恒定的,它在写入之间变化,但总是很大(> 1到2MB)。