7

読み取りと書き込みの両方が必要なゲームサーバーを実装します。したがって、着信接続を受け入れ、aio_read()を使用して読み取りを開始しますが、何かを送信する必要がある場合は、aio_cancel()を使用して読み取りを停止してから、aio_write()使用します。writeのコールバック内で、読み取りを再開します。だから、私はいつも読んでいますが、何かを送る必要があるときは、読書を一時停止します。

これは約20%の時間機能します。それ以外の場合、aio_cancel()の呼び出しは「操作が進行中です」で失敗し、キャンセルできません(永続的なwhileサイクル内であっても)。したがって、追加した書き込み操作は発生しません。

これらの関数をうまく使う方法は?私は何を逃しましたか?

編集:Linux2.6.35で使用されます。Ubuntu10-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を実行する方法は他にもたくさんありますが、ブレインデッドではありません(そうです、aioはブレインデッドです)。たくさんの選択肢; 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()し、非ブロッキングソケットで駆動型イベントループを使用するための推奨事項を2番目に示します。この手法は、多数のクライアントに拡張できることが示されています。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など、ファイル位置が関連付けられていないデバイスの場合、POSIXAIOは未定義です。おそらくそれはあなたの現在の設定でうまくいくかもしれませんが、それは設計によるよりも偶然のようです。

また、Linuxでは、POSIXAIOはユーザースペーススレッドの助けを借りてglibcに実装されています。

つまり、可能であれば、ノンブロッキングIOとepoll()を使用します。ただし、epoll()は、通常のファイルなどのシーク可能なファイル記述子では機能しません(従来のselect()/ poll()でも同じです)。その場合、POSIX AIOは、独自のスレッドプールをローリングする代わりになります。

于 2011-06-28T08:24:08.297 に答える
1

別の読み取りまたは書き込みを行う必要があるという理由だけで、aioの読み取りまたは書き込み要求を停止またはキャンセルする理由はありません。その場合、読み取りまたは書き込み操作をセットアップして次に進むことが主な目的であるため、非同期の読み取りおよび書き込みのポイント全体が無効になります。複数のリクエストをキューに入れることができるため、事前に初期化された一連のデータを取得できる非同期のリーダー/ライタープールをいくつか設定することをお勧めします。aiocb必要なときに非同期操作用にセットアップされた「使用可能な」プールからの構造体。それらが完了すると、別の「終了した」プールに戻り、それらが指すバッファーにアクセスできます。それらが非同期の読み取りまたは書き込みの最中にある間、それらは「ビジー」プールにあり、触れられません。aiocbそうすれば、読み取りまたは書き込み操作を行う必要があるたびにヒープ上に構造を動的に作成し続ける必要はありませんが、それは問題ありません...特定の処理を実行する予定がない場合は、あまり効率的ではありません。制限するか、特定の数の「処理中」のリクエストのみを行うように計画します。

ところで、非同期の読み取り/書き込みハンドラーが実際には別の読み取り/書き込みイベントによって中断される可能性があるという、いくつかの異なる実行中の非同期要求に注意してください。ですから、あなたは本当にあなたのハンドラーでたくさんのことをしたくありません。上記のシナリオでは、ハンドラーは基本的にaiocb、シグナルハンドラーをトリガーした構造体を、リストされた「使用可能」->「ビジー」->「終了」ステージのプールの1つから次のプールに移動します。メインコードはaiocb、「終了した」プール内の構造体が指すバッファーから読み取った後、構造体を「使用可能な」プールに戻します。

于 2011-06-27T20:22:02.947 に答える