AI対戦サーバープログラム作成のヒント

その5 タイムアウトと入力検知

5回目となる今回は、1入力に時間制限をつけるタイムアウトの実装と、 パイプにデータが入っているかを確認する入力検知を学びます。 まず入力検知が必要なのか? から始めていきます。

入力検知の必要性

ReadFile関数はscanf関数やfgets関数と同じく、入力があるまでプログラムの実行を停止します。
プログラムが停止しているということは、他に何も行動が出来ない、ということを指していて、入力がないのにこれらの関数で待機してしまうと、それ以上プログラムが進まなくなってしまいます。
これを防ぐために、パイプやバッファ領域に読み取れるデータが存在することを確認してから、読み取り関数を実行するようにします。

検知関数

では、パイプの中身を確認するにはどうしたらよいでしょうか。
WinAPIでは、PeekNamedPipe関数が存在しますので、これを使います。
この関数は、パイプの中身のあるなしに関わらず、即座に制御を返してくれる関数です。
ですので、この関数を繰り返し入力があったときだけReadFile関数で中身を読み取るようにします。

作るプログラム

今回は、前回のプログラムを少し拡張するだけにします。 具体的には、以下の要素を追加します。

子プロセスを作成する

まず、子プロセスとなるプログラムを作ります。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int input();

int main() {
  int l=0;
  clocck_t start, end;
  fprintf(stderr, "child start.\n");
  while(l<10) {
    int d = input();
    start = clock();
    do { end = clock(); }while(end - start < l*2000)
    printf("%d\n", d*10);
    fflush(stdout);
    l++;
  }
  fprintf(stderr, "child end.\n");
  return 0;
}

int input() {
  char buf[16];
  int d;

  if(fgets(buf, 16-1, stdin) == NULL){
    fprintf(stderr, "C:fgets return NULL\n");
    exit(1);
  }

  d = atoi(buf);

  return d;
}

前回のプログラムとほぼ同じです。
異なる部分は、clock関数を追加して無駄に時間を使っているくらいです。
clock関数はtime.hに含まれていますので、これをインクルードしておきます。

これをコンパイルして出来た実行ファイルを「child.exe」としておきます。

親プロセスを作成する

親プロセスとなるプログラムを作成していきます。
プログラムはやはり前回とほぼ同じ、違うのは、execute関数の処理(後半)部分だけですので、この部分だけをプログラムソースは記述、説明することにします。

execute関数の後半部分

  while(loop<5) {
    int d = (loop+3)*2;
    sprintf(str, "%d\n", d);
    if(!WriteFile(stdinPipe[W], str, strlen(str), &numberOfBytesWritten, NULL)) {
      fprintf(stderr, "WriteFile\n");
      return -1;
    }
    printf("P:send child(%d)\n", d);

    start = clock();
    do {
      if(!PeekNamedPipe(stdoutPipe[R], NULL, 0, NULL, &numberOfBytesRead, NULL)) {
        fprintf(stderr, "PeekNamedPipe\n");
        return -1;
      }
      if(numberOfBytesRead > 0) break;
      end =clock();
    }while(end - start < 5000);
    printf("wait time:%dms\n", end-start);
    if(end - start >= 5000) break;
    if(!ReadFile(stdoutPipe[R], buf, sizeof(buf)-1, &numberOfBytesRead, NULL)) {
      if (GetLastError() == ERROR_BROKEN_PIPE) {
        printf("broken pipe.\n");
        break;
      }else
        fprintf(stderr, "ReadFile\n");
    }
    buf[numberOfBytesRead] = '\0';
    printf("P:recieve:%s", buf);
    loop++;
  }

  if(!TerminateProcess(childProcess, 0)) {
    fprintf(stderr, "TerminateProcess(chileProcess)\n");
    return -1;
  }

  if(!CloseHandle(stdoutPipe[R])) {
    fprintf(stderr, "CloseHandle(stdoutPipe[R])\n");
    return -1;
  }
  stdoutPipe[R] = NULL;
  if(!CloseHandle(stdinPipe[W])) {
    fprintf(stderr, "CloseHandle(stdinPipe[W])\n");
    return -1;
  }
  stdinPipe[W] = NULL;

  return 0;
}

パイプの確認をdo~whileループで回しています。
入力が制限時間以内なら読み取り、オーバーなら処理ループを抜けています。
(外側のwhileから抜けると子プロセスは強制終了される)

実行結果

最後にこのプログラムの実行結果を確認しましょう。 2つの実行ファイルを同じフォルダに入れて親側のプログラムを実行させます。

parent start.
P:send child 6
child start.
P:recieve 60
P:send child 8
P:recieve 80
P:send child 10
P:recieve 100
P:send child 12
P:recieve 120
P:send child 14
P:recieve 140
parent end.

ちゃんと親子間通信で、やりたかったこと(子は親からもらった値の10倍を返す)が達成できています。
また、子プロセスが強制終了されているので、「child end.」の文字は出力されていません。

補足

次回

これで一通りのパイプに関する説明は終わりです。
あとは実際にどうやって運用するのか、などを実例を持って説明できればとか考えています。

前回:その4 複数回通信と強制終了 次回:まとめ・AIのクライアントを作ってみる?

コメント



トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS