7

我在需要读写的地方实现了游戏服务器。所以我接受传入的连接并开始使用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

4 回答 4

6

首先,考虑转储 aio。还有很多其他方法可以进行异步 I/O,它们并不像 Braindead(是的,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

于 2011-06-27T20:01:55.243 回答
6

如果您希望有单独的 AIO 队列用于读取和写入,以便稍后发出的写入可以在之前发出的读取之前执行,那么您可以使用dup()创建套接字的副本,并使用一个来发出读取和另一个发出写道。

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

于 2011-06-28T05:48:00.360 回答
3

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

于 2011-06-28T08:24:08.297 回答
1

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

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

于 2011-06-27T20:22:02.947 回答