C - 如何同时使用aio_read()和aio_write()

时间:2011-06-27 18:24:06

标签: c multithreading sockets networking aio

我实现了我需要读写的游戏服务器。所以我接受传入的连接并使用 aio_read()开始阅读,但是当我需要发送内容时,我会停止使用 aio_cancel()进行阅读,然后使用 aio_write ()即可。在写回调内,我恢复阅读。所以,我一直都在阅读,但是当我需要发送内容时 - 我暂停阅读。

它的工作时间约为20% - 在其他情况下,对 aio_cancel()的调用失败并显示“正在进行操作” - 我无法取消它(即使在永久中< / em>循环)。所以,我添加的写操作永远不会发生。

如何正确使用这些功能?我错过了什么?

编辑: 在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 个答案:

答案 0 :(得分:5)

首先,考虑转储aio 。还有很多其他方法可以做异步I / O而不是脑死亡(是的,aio是breaindead)。很多替代品;如果你在Linux上,你可以使用libaio(io_submit和朋友)。 aio(7)提到了这一点。

回到你的问题 我很长一段时间没有使用aio,但这就是我记得的。 aio_readaio_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

答案 1 :(得分:4)

如果您希望为读取和写入分别设置AIO队列,以便稍后发出的写入可以在先前发出的读取之前执行,那么您可以使用dup()创建套接字的副本,并使用一个发出读取而另一个发出写入。

但是,我提出了完全避免AIO的建议,并简单地使用带有非阻塞套接字的epoll()驱动事件循环。这种技术已被证明可以扩展到大量的客户端 - 如果你的CPU使用率很高,可以对其进行分析并找出发生的位置,因为很可能你的事件循环就是罪魁祸首

答案 2 :(得分:1)

由于您需要进行另一次读取或写入,因此没有理由停止或取消aio读取或写入请求。如果是这种情况,那将破坏异步读写的全部要点,因为它的主要目的是允许您设置读取或写入操作,然后继续。由于可以对多个请求进行排队,因此设置几个异步读取器/写入器池会更好,您可以从“可用”池中获取一组预先初始化的aiocb结构,这些结构已设置为异步在您需要它们时进行操作,然后在完成后将它们返回到另一个“已完成”的池中,您可以访问它们指向的缓冲区。虽然它们处于异步读取或写入的中间,但它们将处于“忙”池中并且不会被触及。这样,每次需要进行读取或写入操作时,您都不必动态地在堆上创建aiocb结构,尽管这样做是可行的...如果您从未计划过,它就不是非常有效超过一定限度,或计划只有一定数量的“飞行中”请求。

BTW,请记住几个不同的正在进行的异步请求,您的异步读/写处理程序实际上可能被另一个读/写事件中断。所以你真的不想和你的处理程序做很多事情。在我描述的上述场景中,您的处理程序基本上将触发信号处理程序的aiocb结构从其中一个池移动到列出的“可用”中的下一个 - &gt;“忙” - &gt;“已完成”阶段。从“完成”池中的aiocb结构指向的缓冲区读取后,您的主代码会将结构移回“可用”池。

答案 3 :(得分:1)

除非我没有弄错,否则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是滚动自己的线程池的替代方法。