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

Is select() + non-blocking write() possible on a blocking pipe or socket?

这种情况是,我有一个阻塞管道或套接字fd,我想将其阻塞到write()而没有阻塞,因此我先执行select(),但这仍然不能保证write()不会阻塞。

这是我收集的数据。即使select()表示
写入是可能的,写入可能会阻塞超过PIPE_BUF个字节。
但是,最多写入PIPE_BUF个字节似乎不会阻塞
实践,但不是POSIX规范要求的。

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

Files reported as ready for writing by select(), poll() or similar
interfaces in this module are guaranteed to not block on a write of up
to PIPE_BUF bytes. This value is guaranteed by POSIX to be at least
512.

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

不幸的是,阻塞套接字也会发生同样的情况。称呼
main()中的test_socket()并使用较大的BUF_BYTES(100000
这里也)。目前尚不清楚是否有安全的缓冲区大小,例如
PIPE_BUF用于套接字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include <sys/socket.h>
#include <netinet/in.h>
#include
#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\
",
           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\
", fd, n_found);
    /* if (n_found == 0) { */
    /*     printf("sleeping\
"); */
    /*     sleep(2); */
    /*     int n_found = probe_with_select(fd + 1, 0, &fdset, 0); */
    /*     printf("retried select() for write on fd %d returned %d\
",  */
    /*            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\
", pipe_fds[0]);
    printf("write side pipe fd: %d\
", pipe_fds[1]);
    for (i = 0; ; i++) {
        printf("i = %d\
", 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\
", 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\
", 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\
", written);
        }
    }
}

int
main(void)
{
    test_pipe();
    /* test_socket(); */
}


除非您希望每当select()说fd准备好进行写操作时一次发送一个字节,否则实际上没有办法知道您将能够发送多少字节,即使这样从理论上讲也是可能的(至少在文档中,如果不是在现实世界中),则可以选择说它已准备好进行写操作,然后在select()和write()之间的时间中改变条件。

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


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

[for pipes] If the O_NONBLOCK flag is clear, a write request may cause the thread to block, but on normal completion it shall return nbyte.

[for streams, which presumably includes streaming sockets] If O_NONBLOCK is clear, and the STREAM cannot accept data (the STREAM write queue is full due to internal flow control conditions), write() shall block until data can be accepted.

因此,您引用的Python文档只能应用于非阻塞模式。但是,由于您不使用Python,因此它毫无关联。


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

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

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

对于阻塞情况

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

因此,直到写入至少1MB的内存后,select才返回。从来没有(我看到)发生过这样的情况:如果随后发生较小的写入,则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。这可能暗示(取决于您的阅读方式)全部或全部。除了它还说select可以用来确定何时可以发送更多数据。所以它不可能全部或全部。

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

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