我实现了我需要读写的游戏服务器。所以我接受传入的连接并使用 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(...);
}
}
答案 0 :(得分:5)
首先,考虑转储aio 。还有很多其他方法可以做异步I / O而不是脑死亡(是的,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
。
答案 1 :(得分:4)
如果您希望为读取和写入分别设置AIO队列,以便稍后发出的写入可以在先前发出的读取之前执行,那么您可以使用dup()
创建套接字的副本,并使用一个发出读取而另一个发出写入。
但是,我提出了完全避免AIO的建议,并简单地使用带有非阻塞套接字的epoll()
驱动事件循环。这种技术已被证明可以扩展到大量的客户端 - 如果你的CPU使用率很高,可以对其进行分析并找出发生的位置,因为很可能不你的事件循环就是罪魁祸首
答案 2 :(得分:1)
由于您需要进行另一次读取或写入,因此没有理由停止或取消aio读取或写入请求。如果是这种情况,那将破坏异步读写的全部要点,因为它的主要目的是允许您设置读取或写入操作,然后继续。由于可以对多个请求进行排队,因此设置几个异步读取器/写入器池会更好,您可以从“可用”池中获取一组预先初始化的aiocb
结构,这些结构已设置为异步在您需要它们时进行操作,然后在完成后将它们返回到另一个“已完成”的池中,您可以访问它们指向的缓冲区。虽然它们处于异步读取或写入的中间,但它们将处于“忙”池中并且不会被触及。这样,每次需要进行读取或写入操作时,您都不必动态地在堆上创建aiocb
结构,尽管这样做是可行的...如果您从未计划过,它就不是非常有效超过一定限度,或计划只有一定数量的“飞行中”请求。
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是滚动自己的线程池的替代方法。