2015-05-06 3 views
3

Ситуация такова, что у меня есть блокирование трубы или гнездо, к которому FD я хочу write() без блокировки, поэтому я делаю select() первый, но это еще не гарантирует, что write() не будет блокировать.Возможно ли() + неблокирование write() на блокирующей трубе или сокете?

Вот данные, которые я собрал. Даже если select() указывает, что запись возможна, запись более PIPE_BUF байтов может блокироваться. Однако запись не более PIPE_BUF байтов, кажется, не блокирует в практике, но она не обязана POSIX spec.

Это указывает только на поведение атомов. Python(!) documentation утверждает, что:

Файлы представлены как готовые для записи на select(), poll() или аналогичных интерфейсов в этом модуле гарантированно не блокировать при записи до до PIPE_BUF байт. Это значение гарантируется POSIX как минимум 512.

В следующей тестовой программы, установите BUF_BYTES сказать 100000 блокировать в write() на Linux, FreeBSD или Solaris после успешного выбора. I предполагают, что именованные каналы имеют аналогичное поведение с анонимными трубами.

К сожалению, это может случиться и с блокирующими гнездами. Позвоните по телефону test_socket() в main() и используйте довольно большой BUF_BYTES (100000 хорошо здесь тоже). Неясно, существует ли безопасный размер буфера, например 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(); */ 
} 
раздел
+2

Есть ли причина, по которой вы не хотите просто устанавливать трубку/сокет в неблокирующий режим и выполняться с ним? (Вы всегда можете вернуть его в режим блокировки после возврата функции write(), если вы считаете, что это необходимо) –

+0

Это в режиме исполнения на языке, поэтому изменение блокировки будет действительно недружелюбным для программиста. – melisgl

+1

Не, если программист никогда не видит изменения ... если каждый раз, когда вы вернетесь к тому, как это было, прежде чем вернуть управление программисту, программист никогда не узнает. (Исключение было бы, если бы несколько потоков использовало тот же самый дескриптор файла одновременно, но проекты, которые делают такие вещи, обречены на горе в любом случае ...;)) –

ответ

1

Если вы не хотите отправлять один байт за один раз, когда select() говорит, что fd готов к записи, нет никакого способа узнать, сколько вы сможете отправить, и даже тогда это теоретически возможно (на по крайней мере, в документации, если не в реальном мире) для выбора, чтобы сказать, что он готов для записи, а затем условие для изменения во времени между select() и write().

Неблокирующее отправление является решением здесь, и вам не нужно менять дескриптор файла в режим неблокирования, чтобы отправить одно сообщение в неблокирующую форму, если вы измените использование метода write() для отправки().Единственное, что вам нужно изменить, - добавить флаг MSG_DONTWAIT к вызову отправки, и это сделает одно сообщение неблокирующим без изменения свойств вашего сокета. Вам даже не нужно использовать select() вообще в этом случае, так как вызов send() предоставит вам всю необходимую информацию в коде возврата - если вы получите код возврата -1, а errno - EAGAIN или EWOULDBLOCK, тогда вы знаете, что больше не можете отправлять.

+0

Хорошо, это должно выполняться при условии, что можно определить, принадлежит ли fd к сокету. Остается ли ограничение PIPE_BUF решить проблему для труб? – melisgl

+0

@melisgl Вы должны заметить, что 'MSG_DONTWAIT' на 'send()' не Posix и может не поддерживаться на всех платформах. – EJP

+0

Нет гарантии, что вы не будете блокировать запись в трубку, сохраняя размер записи в PIPE_BUF. Как вы правильно сказали, это определяет, сколько байтов гарантированно поставляется атомарно, и только совпадение, что буфер канала ядра достаточно велик, если под этим размером, а не с поведением, на которое вы можете положиться. – ckolivas

1

Posix Вы цитируете ясно сказано:

[для труб] Если флаг O_NONBLOCK ясно, запрос на запись может вызвать поток, чтобы блокировать, но при нормальном завершении он возвращает nbyte ,

[для потоков, которые предположительно включают потоковые сокеты] Если O_NONBLOCK чист, и STREAM не может принимать данные (очередь записи STREAM заполнена из-за внутренних условий управления потоком), write() блокируется до тех пор, пока данные не будут приняты ,

Документация Python, которую вы цитируете, может поэтому применяться только к неблокирующему режиму. Но поскольку вы не используете Python, это не имеет никакого отношения.

+0

Документация python может быть правильной, даже если posix не задает поведение, которое он описывает. – melisgl

+1

@melisgl Нет, не может. Все, что он может сделать, это вызвать API C. Если C API блокирует. Python будет блокироваться. – EJP

+0

Я не имел в виду, что Python предоставляет нечто большее, чем тривиальная оболочка вокруг 'select()'. Однако реализация, совместимая с Posix, может по-прежнему обеспечивать гарантии сверх того, что предусмотрено спецификацией. Вот почему ссылался на спецификацию, но попросил получить дополнительную информацию. – melisgl