C - Как использовать как aio_read () и aio_write () - PullRequest
5 голосов
/ 27 июня 2011

Я реализую игровой сервер, где мне нужно и читать, и писать.Поэтому я принимаю входящее соединение и начинаю чтение с него с помощью aio_read () , но когда мне нужно что-то отправить, я прекращаю чтение с помощью aio_cancel () , а затем использую aio_write ().В обратном вызове write я возобновляю чтение.Итак, я все время читаю, но когда мне нужно что-то отправить - я пауза чтение.

Это работает ~ 20% времени - в противном случае вызов aio_cancel() завершается с ошибкой "Операция в процессе" - и я не могу отменить ее (даже в пределах постоянного в то время как цикл ).Итак, моя добавленная операция записи никогда не происходит.

Как правильно использовать эти функции?Что я пропустил?

РЕДАКТИРОВАТЬ: Используется в Linux 2.6.35.Ubuntu 10 - 32 бит.

Пример кода:

void handle_read(union sigval sigev_value) { /* handle data or disconnection */ }
void handle_write(union sigval sigev_value) { /* free writing buffer memory */ }
void start()
{
    const int acceptorSocket = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
    bind(acceptorSocket, (struct sockaddr*)&addr, sizeof(struct sockaddr_in));

    listen(acceptorSocket, SOMAXCONN);

    struct sockaddr_in address;
socklen_t addressLen = sizeof(struct sockaddr_in);

    for(;;)
    {
         const int incomingSocket = accept(acceptorSocket, (struct sockaddr*)&address, &addressLen);
         if(incomingSocket == -1)
         { /* handle error ... */}
         else
         {
              //say socket to append outcoming messages at writing:
              const int currentFlags = fcntl(incomingSocket, F_GETFL, 0);
              if(currentFlags < 0) { /* handle error ... */ }
              if(fcntl(incomingSocket, F_SETFL, currentFlags | O_APPEND) == -1) { /* handle another error ... */ }

              //start reading:
              struct aiocb* readingAiocb = new struct aiocb;
              memset(readingAiocb, 0, sizeof(struct aiocb));
              readingAiocb->aio_nbytes = MY_SOME_BUFFER_SIZE;
              readingAiocb->aio_fildes = socketDesc;
              readingAiocb->aio_buf = mySomeReadBuffer;
              readingAiocb->aio_sigevent.sigev_notify = SIGEV_THREAD;
              readingAiocb->aio_sigevent.sigev_value.sival_ptr = (void*)mySomeData;
              readingAiocb->aio_sigevent.sigev_notify_function = handle_read;
              if(aio_read(readingAiocb) != 0) { /* handle error ... */ }
          }
    }
}

//called at any time from server side:
send(void* data, const size_t dataLength)
{
    //... some thread-safety precautions not needed here ...

    const int cancellingResult = aio_cancel(socketDesc, readingAiocb);
    if(cancellingResult != AIO_CANCELED)
    {
        //this one happens ~80% of the time - embracing previous call to permanent while cycle does not help:
        if(cancellingResult == AIO_NOTCANCELED)
        {
            puts(strerror(aio_return(readingAiocb))); // "Operation now in progress"
            /* don't know what to do... */
        }
    }
    //otherwise it's okay to send:
    else
    {
        aio_write(...);
    }
}

Ответы [ 4 ]

5 голосов
/ 28 июня 2011

Прежде всего, рассмотрите возможность сброса AIO . Есть много других способов сделать асинхронный ввод-вывод, которые не так уж сложны (да, aio - это breaindead). Много альтернатив; если вы используете Linux, вы можете использовать libaio (io_submit и друзья). aio (7) упоминает об этом.

Вернуться к вашему вопросу.
Я давно не пользовался aio, но вот что я помню. aio_read и aio_write оба помещают запросы (aiocb) в некоторую очередь. Они возвращаются немедленно, даже если запросы завершатся через некоторое время. Вполне возможно поставить в очередь несколько запросов, не заботясь о том, что случилось с предыдущими. Итак, в двух словах: прекратите отменять запросы на чтение и продолжайте добавлять их.

/* populate read_aiocb */
rc = aio_read(&read_aiocb);

/* time passes ... */
/* populate write_aiocb */
rc = aio_write(&write_aiocb)

Позже вы можете ждать с помощью aio_suspend, опрашивать с помощью aio_error, ждать сигналов и т. Д.

Я вижу, вы упоминаете epoll в своем комментарии. Вы обязательно должны пойти на libaio.

4 голосов
/ 28 июня 2011

Если вы хотите иметь отдельные очереди AIO для чтения и записи, чтобы запись, выполненная позже, могла выполняться до чтения, выпущенного ранее, тогда вы можете использовать dup() для создания дубликата сокета и использовать одну для выпускаreads, а другой для выдачи write.

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

1 голос
/ 28 июня 2011

Если я не ошибаюсь, POSIX AIO (то есть aio_read (), aio_write () и т. Д.) Гарантированно будет работать только с дескрипторами файлов с возможностью поиска.Из справочной страницы aio_read ():

   The  data  is  read starting at the absolute file offset aiocbp->aio_offset, regardless of the
   current file position.  After this request, the value of the current file position is unspeci‐
   fied.

Для устройств, у которых нет соответствующей позиции в файле, таких как сетевые сокеты, AFAICS, POSIX AIO не определены.Возможно, это работает с вашей текущей настройкой, но это кажется скорее случайным, чем умышленным.

Кроме того, в Linux POSIX AIO реализован в glibc с помощью потоков пользовательского пространства.

То есть, где возможно, использовать неблокирующие IO и epoll ().Однако epoll () не работает для файловых дескрипторов с возможностью поиска, таких как обычные файлы (то же самое относится и к классическим select () / poll ());в этом случае POSIX AIO является альтернативой созданию собственного пула потоков.

1 голос
/ 28 июня 2011

Не должно быть никаких причин останавливать или отменять запрос чтения или записи aio только потому, что вам нужно сделать еще одно чтение или запись.Если бы это было так, это лишило бы смысла асинхронное чтение и запись, поскольку его основная цель - позволить вам настроить операцию чтения или записи, а затем двигаться дальше.Поскольку несколько запросов могут быть поставлены в очередь, было бы намного лучше настроить пару асинхронных пулов устройств чтения / записи, где вы можете получить набор предварительно инициализированных структур aiocb из пула «доступных», которые были настроены для асинхронных операций всякий раз, когдаони нужны вам, а затем возвращают их в другой «готовый» пул, когда они закончили, и вы можете получить доступ к буферам, на которые они указывают.Пока они находятся в середине асинхронного чтения или записи, они находятся в «занятом» пуле и не будут затронуты.Таким образом, вам не нужно будет динамически создавать aiocb структуры в куче каждый раз, когда вам нужно выполнить операцию чтения или записи, хотя это нормально ... это просто не очень эффективно, если вы никогда не планируете работатьсверх определенного лимита или планируйте иметь только определенное количество запросов "в полете".

Кстати, имейте в виду пару различных асинхронных запросов в полете, которые ваш асинхронный обработчик чтения / записи может на самом делебыть прерванным другим событием чтения / записи.Таким образом, вы действительно не хотите много делать со своим обработчиком.В вышеописанном сценарии, который я описал, ваш обработчик в основном переместил бы структуру aiocb, которая запускала обработчик сигнала из одного из пулов в следующий на перечисленных этапах «доступно» -> «занято» -> «завершено».Ваш основной код после чтения из буфера, на который указывают структуры aiocb в «готовом» пуле, затем переместит структуру обратно в «доступный» пул.

...