Асинхронное подключение и отключение с помощью epoll (Linux)

Мне нужно асинхронное подключение и отключение для tcp-клиента с использованием epoll для Linux. Есть доп. функции в Windows, такие как ConnectEx, DisconnectEx, AcceptEx и т. д. В tcp-сервере стандартная функция accept работает, но в tcp-клиенте не работает подключение и отключение... Все сокеты неблокируемые.

Как я могу это сделать?

Спасибо!


person Alexander    schedule 17.04.2012    source источник
comment
Это может вам помочь: stackoverflow.com/questions /2875002/   -  person Skippy Fastol    schedule 17.04.2012
comment
В качестве возможной альтернативы предложениям на связанной странице DJB я хотел бы предложить попробовать dup и close дескриптор (и использовать дубликат). Не проверял, но должно работать, как я понимаю. В документах указано, что не проверять возвращаемое значение close является серьезной ошибкой программирования, поскольку это может вернуть предыдущую ошибку. Это именно то, что вам нужно (если close выдает ошибку, connect не удалось). Хотя, конечно, если вы используете epoll, то у вас гарантированно будет ОС, где getsockopt(SO_ERROR) будет просто работать...   -  person Damon    schedule 17.04.2012
comment
Если возможно, самый простой вариант — дождаться возврата из функции connect(), прежде чем устанавливать NON_BLOCK.   -  person CodeClown42    schedule 17.04.2012
comment
@goldilocks: +1 Не асинхронно, если вы не используете для этого рабочий поток, но я согласен, что простота заманчива. Кроме того, разрешение DNS, которое вам, вероятно, понадобится, в любом случае потребует рабочего потока, если только вы не хотите заблокировать его (getattrinfo_a тоже делает это внутри). Таким образом, хотя вы все равно блокируете работника, вы также можете блокировать его при подключении...   -  person Damon    schedule 17.04.2012
comment
У меня есть 1 рабочий поток для всех моих нужд (сервер/клиент tcp, сокет udp, timerfd). В этой теме я использую epoll для асинхронной работы. Итак, я жду epoll_wait(...) и затем делаю то, что мне нужно. Например: если сокет прослушивает сокет - я вызываю функцию принятия, создаю нового клиента с этим сокетом и добавляю его в очередь epoll. А вот в tcpclient - не могу добавить в epoll до коннекта... А если так сделать - клиент коннектится несколько раз (3-4)...   -  person Alexander    schedule 17.04.2012


Ответы (4)


Чтобы сделать неблокирующий connect(), предполагая, что сокет уже сделан неблокирующим:

int res = connect(fd, ...);
if (res < 0 && errno != EINPROGRESS) {
    // error, fail somehow, close socket
    return;
}

if (res == 0) {
    // connection has succeeded immediately
} else {
    // connection attempt is in progress
}

Во втором случае, когда connect() завершился с ошибкой EINPROGRESS (и только в этом случае), вам нужно дождаться, пока сокет станет доступным для записи, например. для epoll укажите, что вы ожидаете EPOLLOUT на этом сокете. Как только вы получите уведомление о том, что он доступен для записи (с epoll также ожидается получение события EPOLLERR или EPOLLHUP), проверьте результат попытки подключения:

int result;
socklen_t result_len = sizeof(result);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &result, &result_len) < 0) {
    // error, fail somehow, close socket
    return;
}

if (result != 0) {
    // connection failed; error code is in 'result'
    return;
}

// socket is ready for read()/write()

По моему опыту, в Linux функция connect() никогда не срабатывает сразу, и вам всегда приходится ждать возможности записи. Однако, например, во FreeBSD я видел, как неблокирующий метод connect() с локальным хостом срабатывал сразу.

person Ambroz Bizjak    schedule 17.04.2012
comment
@ Мэтт, я знаю, что да, ты, вероятно, делаешь что-то не так. Что именно вы пытаетесь сделать, где это терпит неудачу? Вы перевели сокет в неблокирующий режим с помощью fcntl? - person Ambroz Bizjak; 25.10.2012
comment
Упс, неважно. Баг с моей стороны. Да, у меня были неблокирующие сокеты. У меня была ошибка при проверке результата соединения! классическая ошибка c. - person hookenz; 26.10.2012
comment
Так же, как FYI: в случае сбоя неблокирующего подключения Windows уведомит об исключительном условии, а не о возможности записи сокета. msdn.microsoft.com/en-us/library/windows/ рабочий стол/ms740141.aspx - person zapstar; 09.02.2018

По опыту, при обнаружении неблокирующего соединения epoll немного отличается от select и poll.

с эполлом:

После выполнения вызова connect() проверьте код возврата.

Если соединение не может быть завершено немедленно, зарегистрируйте событие EPOLLOUT с помощью epoll.

Вызовите epoll_wait().

если соединение не удалось, ваши события будут заполнены EPOLLERR или EPOLLHUP, в противном случае будет запущен EPOLLOUT.

person Bugs    schedule 15.08.2013
comment
Да, я забыл упомянуть в своем ответе, что epoll может возвращать EPOLLERR или EPOLLHUP в дополнение к EPOLLOUT. Спасибо за упоминание, исправлено. - person Ambroz Bizjak; 08.10.2013

У меня есть «полный» ответ здесь на случай, если кто-то еще ищет это:

#include <sys/epoll.h>
#include <errno.h>
....
....
int retVal = -1;
socklen_t retValLen = sizeof (retVal);

int status = connect(socketFD, ...);
if (status == 0)
 {
   // OK -- socket is ready for IO
 }
else if (errno == EINPROGRESS)
 {
    struct epoll_event newPeerConnectionEvent;
    int epollFD = -1;
    struct epoll_event processableEvents;
    unsigned int numEvents = -1;

    if ((epollFD = epoll_create (1)) == -1)
    {
       printf ("Could not create the epoll FD list. Aborting!");
       exit (2);
    }     

    newPeerConnectionEvent.data.fd = socketFD;
    newPeerConnectionEvent.events = EPOLLOUT | EPOLLIN | EPOLLERR;

    if (epoll_ctl (epollFD, EPOLL_CTL_ADD, socketFD, &newPeerConnectionEvent) == -1)
    {
       printf ("Could not add the socket FD to the epoll FD list. Aborting!");
       exit (2);
    }

    numEvents = epoll_wait (epollFD, &processableEvents, 1, -1);

    if (numEvents < 0)
    {
       printf ("Serious error in epoll setup: epoll_wait () returned < 0 status!");
       exit (2);
    }

    if (getsockopt (socketFD, SOL_SOCKET, SO_ERROR, &retVal, &retValLen) < 0)
    {
       // ERROR, fail somehow, close socket
    }

    if (retVal != 0) 
    {
       // ERROR: connect did not "go through"
    }   
}
else
{
   // ERROR: connect did not "go through" for other non-recoverable reasons.
   switch (errno)
   {
     ...
   }
}
person Sonny    schedule 05.06.2012
comment
Я считаю, что ваша проверка ошибок после epoll_wait() неверна - вы всегда должны проверять результат попытки подключения через getsockopt(SO_ERROR), даже если вы не получили EPOLLERR. См. EINPROGRESS на справочной странице linux.die.net/man/2/connect. , assert() - это неправильный способ обработки критических ошибок - это означало бы, что вы доказали, что это никогда не может произойти. Вместо этого используйте функцию exit(), которая завершит программу, даже если NDEBUG определен. - person Ambroz Bizjak; 06.06.2012
comment
Просто добавил предложенные правки. Неотредактированная версия, кажется, работает для меня. - person Sonny; 06.06.2012
comment
Добавление -1 в качестве тайм-аута, не должен ли epoll_wait блокироваться на неопределенный срок в приведенной выше программе? - person wonder; 30.11.2019

Я попробовал решение Сонни, и epoll_ctl вернет недопустимый аргумент. Поэтому я думаю, что, возможно, правильный способ сделать это выглядит следующим образом:

1.создать socketfd и epolfd

2. Используйте epoll_ctl, чтобы связать socketfd и epollfd с событием epoll.

3. подключиться (socketfd,...)

4.проверьте возвращаемое значение или errno

5.если errno == EINPROGRESS, выполните epoll_wait

person Aaron    schedule 03.05.2014