Использование select() для неблокирующих сокетов

Я пытаюсь использовать функцию выбора, чтобы иметь неблокирующий ввод-вывод между сервером и 1 клиентом (не более), где связь протекает хорошо (может отправить в любое время, а другой получит, не дожидаясь отправки). Я нашел учебник с некоторым кодом и попытался адаптировать его к себе. Это то, что у меня есть -

Сервер

#define PORT "4950"
#define STDIN 0

struct sockaddr name;

void set_nonblock(int socket) {
    int flags;
    flags = fcntl(socket,F_GETFL,0);
    assert(flags != -1);
    fcntl(socket, F_SETFL, flags | O_NONBLOCK);
}


// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET)
        return &(((struct sockaddr_in*)sa)->sin_addr);

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int agrc, char** argv) {
    int status, sock, adrlen, new_sd;

    struct addrinfo hints;
    struct addrinfo *servinfo;  //will point to the results

    //store the connecting address and size
    struct sockaddr_storage their_addr;
    socklen_t their_addr_size;

    fd_set read_flags,write_flags; // the flag sets to be used
    struct timeval waitd;          // the max wait time for an event
    int sel;                      // holds return value for select();

    //socket infoS
    memset(&hints, 0, sizeof hints); //make sure the struct is empty
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM; //tcp
    hints.ai_flags = AI_PASSIVE;     //use local-host address

    //get server info, put into servinfo
    if ((status = getaddrinfo("127.0.0.1", PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        exit(1);
    }

    //make socket
    sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
    if (sock < 0) {
        printf("\nserver socket failure %m", errno);
        exit(1);
    }

    //allow reuse of port
    int yes=1;
    if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }

    //unlink and bind
    unlink("127.0.0.1");
    if(bind (sock, servinfo->ai_addr, servinfo->ai_addrlen) < 0) {
        printf("\nBind error %m", errno);
        exit(1);
    }

    freeaddrinfo(servinfo);

    //listen
    if(listen(sock, 5) < 0) {
        printf("\nListen error %m", errno);
        exit(1);
    }
    their_addr_size = sizeof(their_addr);

    //accept
    new_sd = accept(sock, (struct sockaddr*)&their_addr, &their_addr_size);
    if( new_sd < 0) {
        printf("\nAccept error %m", errno);
        exit(1);
    }

    set_nonblock(new_sd);
    cout<<"\nSuccessful Connection!";

    char* in = new char[255];
    char* out = new char[255];
    int numSent;
    int numRead;


    while(1) {

        waitd.tv_sec = 10;
        FD_ZERO(&read_flags);
        FD_ZERO(&write_flags);
        FD_SET(new_sd, &read_flags);

        if(strlen(out) != 0)
            FD_SET(new_sd, &write_flags);

        sel = select(new_sd+1, &read_flags, &write_flags, (fd_set*)0, &waitd);
        if(sel < 0)
            continue;

        //socket ready for reading
        if(FD_ISSET(new_sd, &read_flags)) {
            FD_CLR(new_sd, &read_flags);

            memset(&in, 0, sizeof(in));

            if(recv(new_sd, in, sizeof(in), 0) <= 0) {
                close(new_sd);
                break;
            }
            else
                cout<<"\n"<<in;
        }   //end if ready for read

        //socket ready for writing
        if(FD_ISSET(new_sd, &write_flags)) {

            FD_CLR(new_sd, &write_flags);
            send(new_sd, out, strlen(out), 0);
            memset(&out, 0, sizeof(out));
        }
    }   //end while

    cout<<"\n\nExiting normally\n";
    return 0;
}

Клиент (в основном то же самое, только без приема вызова) -

#define PORT "4950"

struct sockaddr name;

void set_nonblock(int socket) {
    int flags;
    flags = fcntl(socket,F_GETFL,0);
    assert(flags != -1);
    fcntl(socket, F_SETFL, flags | O_NONBLOCK);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET)
        return &(((struct sockaddr_in*)sa)->sin_addr);

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int agrc, char** argv) {
    int status, sock, adrlen;

    struct addrinfo hints;
    struct addrinfo *servinfo;  //will point to the results

    fd_set read_flags,write_flags; // the flag sets to be used
    struct timeval waitd;          // the max wait time for an event
    int sel;                      // holds return value for select();

    memset(&hints, 0, sizeof hints); //make sure the struct is empty
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM; //tcp
    hints.ai_flags = AI_PASSIVE;     //use local-host address

    //get server info, put into servinfo
    if ((status = getaddrinfo("127.0.0.1", PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        exit(1);
    }

    //make socket
    sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
    if (sock < 0) {
        printf("\nserver socket failure %m", errno);
        exit(1);
    }

    if(connect(sock, servinfo->ai_addr, servinfo->ai_addrlen) < 0) {
        printf("\nclient connection failure %m", errno);
        exit(1);
    }

    cout<<"\nSuccessful connection!";

    set_nonblock(sock);

    char* out = new char[255];
    char* in = new char[255];
    int numRead;
    int numSent;

    while(1) {

        waitd.tv_sec = 10;
        FD_ZERO(&read_flags);
        FD_ZERO(&write_flags);
        FD_SET(sock, &read_flags);

        if(strlen(out) != 0)
            FD_SET(sock, &write_flags);


        sel = select(sock+1, &read_flags, &write_flags, (fd_set*)0, &waitd);
        if(sel < 0)
            continue;

        //socket ready for reading
        if(FD_ISSET(sock, &read_flags)) {
            FD_CLR(sock, &read_flags);

            memset(&in, 0, sizeof(in));

            if(recv(sock, in, sizeof(in), 0) <= 0) {
                close(sock);
                break;
            }
            else
                cout<<"\n"<<in;
        }   //end if ready for read

        //socket ready for writing
        if(FD_ISSET(sock, &write_flags)) {
            FD_CLR(sock, &write_flags);
            send(sock, out, strlen(out), 0);
            memset(&out, 0, sizeof(out));
        }
    }   //end while

    cout<<"\n\nExiting normally\n";
    return 0;
}

Проблема в том, что когда я запускаю их, ничего не происходит. Я могу ввести один и нажать Enter, а на другом экране ничего не появляется (и наоборот). Это не вся информация для отладки, и это моя первая реальная попытка использовать select, поэтому я подумал, что, может быть, я просто не знаю чего-то простого. Если что-то может быть замечено как неправильное или странное, пожалуйста, укажите это, любая помощь приветствуется.


person Sterling    schedule 16.07.2011    source источник


Ответы (3)


Проблема в том, что когда я запускаю их, ничего не происходит.

Настоящая проблема в том, что люди годами вставляли материал из Beej, не понимая его. Вот почему мне не очень нравится это руководство; он дает большие блоки кода, не объясняя их подробно.

Вы ничего не читаете и ничего не отправляете; нет fgets, scanf, cin и т. д. Вот что я бы сделал:

FD_SET(sock, &read_flags);
FD_SET(STDIN_FILENO, &read_flags);

/* .. snip .. */

if(FD_ISSET(STDIN_FILENO, &read_flags)) {
    fgets(out, len, stdin);
}

Это будет отслеживать stdin и читать из него, когда доступен ввод; затем, когда сокет доступен для записи (FD_ISSET(sock, &write_flags)), он отправит буфер.

person cnicutar    schedule 16.07.2011
comment
Спасибо за ответ. Да, я читал туториал Биджа последние несколько дней и едва справляюсь. Я попытался добавить это в свой код, но теперь я получаю ошибку сегментации всякий раз, когда я что-то ввожу в любой из терминалов на этом терминале (введите «привет» на сервер, ошибки сегмента сервера, клиент закрывается нормально, и наоборот). Я попробовал getline, чтобы убедиться, что это не просто fgets. GDB на самом деле не помогает - говорит мне, что обратная трассировка - это просто # 0 0x08048c93 в main (). Извините, что спрашиваю (немного отчаянно на данный момент), но знаете ли вы, что может быть причиной этого? - person Sterling; 16.07.2011
comment
@Sterling компилируется с использованием -g. Также для начала объявите out и in так: char out[255];. - person cnicutar; 16.07.2011
comment
странно... когда я просто меняю эти объявления, программа не ошибается, но проходит прямо через цикл while и выходит. - person Sterling; 16.07.2011
comment
@Sterling Попробуйте отладить его. Добавьте printf строк, если нужно. Добавьте строку printf сразу после выбора printf("Select fired\n"); и т. д. - person cnicutar; 16.07.2011
comment
Сделаю. Еще одна странная вещь, которую, возможно, стоит отметить... select срабатывает разное количество раз для двух программ и разное количество раз каждый раз, когда я запускаю его. Иногда 10, иногда 20, иногда 5, затем закрывает сокет. Каким-то образом вызов recv равен ‹= 0, и я ничего не ввожу... также не меняется, когда я оставляю сокеты блокирующими. - person Sterling; 16.07.2011
comment
Конечно, он не использует fgets, cin и т. д., но он вызывает recv. Почему этого будет недостаточно? - person EntangledLoops; 12.10.2015
comment
Я нашел более информативное руководство: ibm.com/support/knowledgecenter/ssw_i5_54. /rzab6/xnonblock.htm - person Guy Avraham; 11.09.2016

У меня теперь программа работает корректно.

сервер -

#define PORT "4950"
#define STDIN 0

struct sockaddr name;

void set_nonblock(int socket) {
    int flags;
    flags = fcntl(socket,F_GETFL,0);
    assert(flags != -1);
    fcntl(socket, F_SETFL, flags | O_NONBLOCK);
}


// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET)
        return &(((struct sockaddr_in*)sa)->sin_addr);

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}


int main(int agrc, char** argv) {
    int status, sock, adrlen, new_sd;

    struct addrinfo hints;
    struct addrinfo *servinfo;  //will point to the results

    //store the connecting address and size
    struct sockaddr_storage their_addr;
    socklen_t their_addr_size;

    fd_set read_flags,write_flags; // the flag sets to be used
    struct timeval waitd = {10, 0};          // the max wait time for an event
    int sel;                      // holds return value for select();


    //socket infoS
    memset(&hints, 0, sizeof hints); //make sure the struct is empty
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM; //tcp
    hints.ai_flags = AI_PASSIVE;     //use local-host address

    //get server info, put into servinfo
    if ((status = getaddrinfo("127.0.0.1", PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        exit(1);
    }

    //make socket
    sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
    if (sock < 0) {
        printf("\nserver socket failure %m", errno);
        exit(1);
    }

    //allow reuse of port
    int yes=1;
    if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }

    //unlink and bind
    unlink("127.0.0.1");
    if(bind (sock, servinfo->ai_addr, servinfo->ai_addrlen) < 0) {
        printf("\nBind error %m", errno);
        exit(1);
    }

    freeaddrinfo(servinfo);

    //listen
    if(listen(sock, 5) < 0) {
        printf("\nListen error %m", errno);
        exit(1);
    }
    their_addr_size = sizeof(their_addr);

    //accept
    new_sd = accept(sock, (struct sockaddr*)&their_addr, &their_addr_size);
    if( new_sd < 0) {
        printf("\nAccept error %m", errno);
        exit(1);
    }

    //set non blocking
    set_nonblock(new_sd);
    cout<<"\nSuccessful Connection!\n";

    char in[255];
    char out[255];
    memset(&in, 0, 255);
    memset(&out, 0, 255);
    int numSent;
    int numRead;

    while(1) {

        FD_ZERO(&read_flags);
        FD_ZERO(&write_flags);
        FD_SET(new_sd, &read_flags);
        FD_SET(new_sd, &write_flags);
        FD_SET(STDIN_FILENO, &read_flags);
        FD_SET(STDIN_FILENO, &write_flags);

        sel = select(new_sd+1, &read_flags, &write_flags, (fd_set*)0, &waitd);

        //if an error with select
        if(sel < 0)
            continue;

        //socket ready for reading
        if(FD_ISSET(new_sd, &read_flags)) {

            //clear set
            FD_CLR(new_sd, &read_flags);

            memset(&in, 0, 255);

            numRead = recv(new_sd, in, 255, 0);
            if(numRead <= 0) {
                printf("\nClosing socket");
                close(new_sd);
                break;
            }
            else if(in[0] != '\0')
                cout<<"\nClient: "<<in;

        }   //end if ready for read

        //if stdin is ready to be read
        if(FD_ISSET(STDIN_FILENO, &read_flags))
            fgets(out, 255, stdin);


        //socket ready for writing
        if(FD_ISSET(new_sd, &write_flags)) {
            //printf("\nSocket ready for write");
            FD_CLR(new_sd, &write_flags);
            send(new_sd, out, 255, 0);
            memset(&out, 0, 255);
        }   //end if
    }   //end while

   cout<<"\n\nExiting normally\n";
    return 0;
}

Клиент в основном такой же... разница только в том, что он не слушает и не принимает.

person Sterling    schedule 18.07.2011
comment
Я слишком устал, чтобы объяснять это, поэтому я просто опубликую код для сравнения с приведенным выше кодом, если кому-то в будущем понадобится информация, это не способ написать полезный ответ -1 - person Manu343726; 30.09.2015
comment
В этом разделе: FD_SET(new_sd, &read_flags); FD_SET(new_sd, &write_flags); FD_SET(STDIN_FILENO, &read_flags); FD_SET(STDIN_FILENO, &write_flags); Первые 2 FD_SET замещаются вторыми двумя. Зачем беспокоиться? - person EntangledLoops; 12.10.2015
comment
@EntangledLoops Я не согласен - эти четыре строки добавят new_sd и STDIN_FILENO в наборы read_flags и write_flags. Из справочных страниц fd_set: FD_SET() и FD_CLR() соответственно добавляют и удалить данный файловый дескриптор из набора. - person pepan; 12.05.2016
comment
@pepan Вы, очевидно, правы, когда я смотрю на это сейчас; не помню, о чем я думал, когда читал ее в октябре. +1 за хороший пример - person EntangledLoops; 13.05.2016

Я просто хочу добавить, что приведенный выше пример может не работать должным образом в Linux. В Linux waitd можно изменить с помощью select. Таким образом, для Linux перед выбором следует переписать waitd.

См. раздел «тайм-аут» на странице руководства для выбора.

person Henk    schedule 08.07.2016