2011-06-27 19 views
5

我实现了游戏服务器,我需要读取和写入。所以我接受传入的连接,并开始使用的aio_read(它读)但是当我需要送东西,我停止使用调用aio_cancel(读取)然后用aio_write()。在书写的回调中,我继续阅读。所以,我一直在阅读,但当我需要发送一些东西 - 我暂停阅读。C - 如何使用aio_read()和aio_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(...); 
    } 
} 
+0

你应该指定这是哪个操作系统。也尝试创建一个显示问题的最小自包含示例。否则,你确定你需要取消它吗?我认为完全可以在同一个文件描述符上同时读取和写入操作。 –

+0

在C++中,我使用两个线程同时在同一个套接字上操作。一位读者,一位作家。我认为这也可以与aio一起工作,但我不确定,但也许这可以回答某人。 –

+0

你能否解释一下你为什么需要暂停阅读? aio不允许您发出无取消阅读的写入请求吗? –

回答

3

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

但是,我第二次提出避免AIO的建议,只需使用带有非阻塞套接字的驱动的事件循环即epoll()。这种技术已被证明可以扩展到大量的客户端 - 如果您的CPU使用率很高,请对其进行分析并找出发生的情况,因为这可能是因为您的事件循环是导致事件发生的而不是

+0

我用'dup()',它工作。谢谢。因此,现在我有两个实现 - 将在实时项目中进行测试并进行比较(网络部分实现为可在编译时切换的独立模块)。 – Slav

4

首先,考虑倾销AIO。有很多其他方法可以做异步I/O,这些不是braindead(是的,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_error使用aio_suspend,民意调查,以等待,等待我看到你在您的评论提到epoll信号等

你一定要去libaio

+0

谢谢。什么是“读写读写”链?我能不能在两者之间进行阅读两次?目前我面对的是真正的队列 - 我不能等待read_handler(我做aio_write(),但它没有做任何事,直到aio_read()完成它的工作 - 从客户端读取至少一个字节)。我会看看** libaio **。 – Slav

+0

http://lse.sourceforge.net/io/aio.html - 这是关于libaio的吗?如果是这样,它在**“什么不工作?”**菜单中有**“AIO在套接字上读取和写入”**子句。我错过了什么吗? – Slav

+0

你能给我一个灵吗? – Slav

1

应该没有理由停止或取消aio读取或写入请求,因为您需要重新读取或写入。如果是这样的话,那将会破坏异步读写的全部要点,因为它的主要目的是让您设置读取或写入操作,然后继续。由于多个请求可以排队,因此设置几个异步读/写池将会好得多,您可以从一个“可用”池中获取一组预先初始化的结构,这些池已经为异步操作设置,只要您需要他们,然后在他们完成时将他们返回到另一个“完成”池,并且您可以访问他们指向的缓冲区。当他们处于异步读或写的中间时,他们将处于“繁忙”池中,并且不会被触摸。这样,每次需要进行读取或写入操作时,您都不必在堆上动态创建aiocb结构,尽管这没关系......如果您从未打算过度某些限制,或计划只有一定数量的“机上”请求。

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

0

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