Включить неблокирующий сокет

У меня есть сервер, написанный на C / C ++. Я установил обертку для подключения следующим образом:

    //START WRAPPER
    void Server::init_address(int port)
    {
memset(&(this->serv_addr), 0, sizeof(this->serv_addr));
this->serv_addr.sin_family = AF_INET;
this->serv_addr.sin_port = htons(port);
this->serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    }


    int Server::w_socket()
    {
int retv;
retv = socket(PF_INET, SOCK_STREAM, 0);
//FIXME
//fcntl(retv, F_SETFL, O_NONBLOCK);
if(retv == -1)
{
    std::string err_msg(strerror(errno));
    err_msg = "[socket] " + err_msg;
    throw err_msg;
}
else
{
    int reuse_opt = 1;

    if(setsockopt(retv, SOL_SOCKET, SO_REUSEADDR, &reuse_opt, sizeof(int))==-1)
    {
        perror("setsockopt error");
        exit(1);
    }
    return retv;
}
    }

    void Server::w_bind()
    {
int retv;
retv = bind(this->sock_fd,
        (struct sockaddr*) &(this->serv_addr),
        sizeof(this->serv_addr));

if(retv == -1)
{
    std::string err_msg(strerror(errno));
    err_msg = "[bind] " + err_msg;
    throw err_msg;
}
    }

    void Server::w_listen()
    {
int retv;
retv = listen(this->sock_fd, 3);
if (retv == -1)
{
    std::string err_msg(strerror(errno));
    err_msg = "[listen] " + err_msg;
    throw err_msg;
}
    }

    int Server::w_accept(struct sockaddr_in* client_addr)
    {
int retv;
int socklen = sizeof(sockaddr_in);

retv = accept(this->sock_fd, (struct sockaddr*)client_addr, (socklen_t*)&socklen);
if(retv == -1)
{
    std::string err_msg(strerror(errno));
    err_msg = "[accept] " + err_msg;
    throw err_msg;
}
else
{
    return retv;
}
            }

    int Server::recvtimeout(int s, char *buf, int len, int timeout)
    {
fd_set fds;
int n;
struct timeval tv;
// set up the file descriptor set
FD_ZERO(&fds);
FD_SET(s, &fds);
// set up the struct timeval for the timeout
tv.tv_sec = timeout;
tv.tv_usec = 0;
// wait until timeout or data received
n = select(s+1, &fds, NULL, NULL, &tv);
if (n == 0){
    return -2; // timeout!
}
if (n == -1){
    return -1; // error
}
// data must be here, so do a normal recv()
return recv(s, buf, len, 0);
    }
    // END WRAPPER

Моя цель - включить неблокирующий режим сокета. Я попытался выполнить fcntl (retv, F_SETFL, O_NONBLOCK);, как сказано в руководстве Beej, но я получаю сообщение об ошибке: [accept] Ресурс временно недоступен Решение этой проблемы Проблема заключается в использовании функции select, но я уже использую ее в функции recvtimeout, как всегда, как сказал Beej guide.

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


person user840718    schedule 29.05.2012    source источник


Ответы (2)


Вы получаете сообщение об ошибке потому, что сокет не блокируется.

Либо вы выполняете цикл занятости, пока получаете эту ошибку (проверьте errno, когда accept возвращает -1 для EWOULDBLOCK или EAGAIN). Другое и рекомендуемое решение - использовать select, чтобы узнать, доступен ли сокет для чтения, после чего вы можете вызвать accept.

Изменить: как это сделать с помощью select

У вас должен быть цикл событий на более высоком уровне, который проверяет, можно ли читать из прослушивающего сокета или подключенных сокетов. Если прослушивающий сокет доступен для чтения, вы можете принять новое соединение, а если подключенные сокеты доступны для чтения, вы можете читать из них.

Что-то типа:

for (;;)
{
    fd_set readset;

    FD_ZERO(&readset); 
    FD_SET(listening_socket, &readset);
    int maxfd = listening_socket;

    if (connected_socket >= 0)
    {
        FD_SET(connected_socket, &readset);
        maxfd = max(maxfd, connected_socket);
    }

    // NULL timeout (5-th argument) means wait until event
    select(maxfd + 1, &readset, NULL, NULL, NULL);

    if (FD_ISSET(listening_socket, &readset))
    {
        accept_new_connection(listening_socket);
    }

    if (connected_socket >= 0 && FD_ISSET(connected_socket, &readset))
    {
        if (!read_from_socket(connected_socket))
        {
            close(connected_socket);
            connected_socket = -1;
        }
    }
}

Если у вас несколько подключенных сокетов, поместите их в простой связанный список и добавляйте / проверяйте их в цикле. Удалять из списка, когда они закрываются.

person Some programmer dude    schedule 29.05.2012
comment
Что ж, я проверил errno для EAGAIN, как в этом примере, в функции accept с помощью оператора do while, stackoverflow.com/questions/735249/ Я раскомментировал fcntl (retv, F_SETFL, O_NONBLOCK); Сейчас вроде все работает, а разве не блокирует? Я отключил свой сервер, но он все еще вылетает. О втором решении для использования select, чтобы увидеть, когда сокет доступен для чтения, как я могу это использовать? Моего выбора в функции recvtimeout недостаточно для этого? Спасибо. - person user840718; 29.05.2012
comment
Хорошо, последний вопрос. В моем случае, могу ли я изменить функцию recvtimeout или выполнить другую функцию с другим выбором? Потому что оба являются высокоуровневыми, когда я вызываю прием. - person user840718; 29.05.2012

В своем решении вы используете вызов select для сокета подключения, но не используете его для listen socket.

Если вы изменили listen socket также на неблокирующий, то вы должны использовать select для listen socket перед вызовом accept.

person Jay    schedule 29.05.2012