EzGraph : Eazy Graphic Library

概要 チュートリアル 機能一覧 関数名一覧 機能別一覧 色見本 MMLの書き方

ネットワーク機能について

他のプロセスとの通信が発生するとき、必ずしも相手がすぐに応答してくれるとは限りません。 相手からのメッセージを待ち受けつつ、ユーザーの入力に対処し、画面を更新したり内部処理をしたい場合がほとんどでしょう。

そこで、EzGraphではイベントドリブンのプログラミングスタイルにのっとってネットワークを用いた通信を行います。

サーバーとクライアント

接続を待ち受ける側をサーバー、サーバーに接続しに行く側をクライアントと呼びます。

最初に接続を待ち受けることを言うのであって、いったん確立した接続にメッセージを送るのはどちらの側からでもよいです。

以下に、サーバー側で接続を待ち受ける例を示します

  EzSetAcceptHandler(accept_handler);   /* 接続受け入れイベント */
  EzListen(PORT);                       /* 接続をPORT番のポートで待ち受ける */

クライアント側で接続しに行くコード例

  /* server_address で動作して、PORT番のポートで
     接続を待ち受けているサーバーに接続する */
  server = EzConnect(server_address, PORT);

ネットワーク機能のイベントについて

Accept(接続受け入れ)イベント

EzListen関数を用いて接続を待ち受けるとき、新しいクライアントからの接続があると発生するイベントです。EzSetAcceptHandler()関数を使ってイベント・ハンドラを登録していないと接続を受理しません。

クライアントを、FILE* で表される接続で取り扱います。こちらから相手にむけてメッセージを送りたい場合は、FILE* を変数に持っておいてください。

接続を受け入れたときの処理の例

void accept_handler(FILE* client) {
  /* クライアントのリストに登録 */
  add_new_client(client);
}

新しい接続があったことの通知がイベントになっているため、 サーバーはクライアントからの新しい接続を待ちながら、その間に画面を描画して クライアントのリストを表示したり、ユーザーの操作を処理したり、既に接続している クライアントと通信したりできます。

Receive(受信)イベント

EzGraphでは通信相手との接続を FILE* として扱いますが、もし何もメッセージが 送られていないときにはfgets()関数やfscanf()関数などはメッセージを 受信するまでプログラムの進行をそこで止めてしまいます(ブロッキング)。

そこで、ある確立した接続について読み取れるメッセージが届いたら 受信イベントが発生するので、そこではじめてメッセージを読みにいく ようにします。

void receive_handler(FILE* client) { /* メッセージを受信したとき */
  int i;
  char buf[BUF_MAX];
  fgets(buf, BUF_MAX, client);  /* fgets関数で取得 */
  for (i = 0; i < CLIENT_MAX; i++) { /* 全クライアントに対して */
    if (clients[i] == NULL) continue;
    fprintf(clients[i], "%s", buf); /* メッセージをそのまま送信 */
  }
}

ConnectionClose(切断)イベント

通信相手との接続が切れたあと、次のタイマーイベントまでに発生するイベントです。

たくさんの相手と同時に通信する場合には通信相手のリストから切断された相手を 削除する必要があるでしょうし、接続が無二のもので切断されたらもはやプログラムを 動かしている意味がないならイベントループを抜けたいでしょう。

実際には、イベントの発生前に通信の切断が起こっていて、相手からのメッセージを 読み取る途中で失敗することがあるので、fgets()関数やfscanf()関数などの 戻り値をきちんと確かめることを強く推奨します。

/* 接続が閉じたとき呼ばれる */
void close_handler(FILE* server) {
  puts("server closed");
  EzExitEventLoop();            /* プログラム終了 */
}

サンプルプログラム

サーバー側のサンプル

chat_server.c
#include <EzGraph.h>
#include <stdio.h>

#define PORT 6002
#define BUF_MAX 8192
#define CLIENT_MAX 32

FILE* clients[CLIENT_MAX];

void add_new_client(FILE* client) {
  int i;
  for (i = 0; i < CLIENT_MAX; i++) {
    if (clients[i] == NULL) {
      clients[i] = client;
      return;
    }
  }
}

void remove_client(FILE* client) {
  int i;
  for (i = 0; i < CLIENT_MAX; i++) {
    if (clients[i] == client) {
      clients[i] = NULL;
      break;
    }
  }
}

void receive_handler(FILE* client) { /* メッセージを受信したとき */
  int i;
  char buf[BUF_MAX];
  fgets(buf, BUF_MAX, client);  /* fgets関数で取得 */
  for (i = 0; i < CLIENT_MAX; i++) { /* 全クライアントに対して */
    if (clients[i] == NULL) continue;
    fprintf(clients[i], "%s", buf); /* メッセージをそのまま送信 */
  }
}

void close_handler(FILE* client) {
  /* クライアントのリストから削除 */
  remove_client(client);
}

void accept_handler(FILE* client) {
  /* クライアントのリストに登録 */
  add_new_client(client);
}

int main(void) {
  EzSetAcceptHandler(accept_handler); /* 接続受け入れイベント */
  EzSetReceiveHandler(receive_handler); /* メッセージ受信イベント */
  EzSetConnectionCloseHandler(close_handler); /* 切断イベント */
  EzListen(PORT);               /* 接続をPORT番のポートで待ち受ける */
  EzEventLoop();
  return 0;
}

クライアント側のサンプル

chat_client.c
#include <EzGraph.h>
#include <stdio.h>
#include <ctype.h>

#define PORT 6002
#define BUF_MAX 8192
#define LINES 64
#define CHARS 80

FILE *server;
int ring_first = 0, str_index;
char *name, texts[LINES][CHARS], unsend_string[BUF_MAX];

void draw(void) {
  int i;
  for (i = 0; i < LINES; i++) {
    EzDrawStringB(0, (i + 2.5) * 16, texts[(i + ring_first + 1) % LINES]);
  }
  EzDrawStringB(0, 16, unsend_string);
  EzShowBuffer();
}

void receive_handler(FILE* server) { /* メッセージを受け取ったとき */
  int i;
  char buf[BUF_MAX];
  fgets(buf, BUF_MAX, server);  /* fgets関数で一行取得 */
  for (i = 0; (buf[i] != '\n') && (i < CHARS-1); i++)
    texts[ring_first][i] = buf[i]; /* CHARS 文字ぶんコピー */
  texts[ring_first][i] = '\0';
  ring_first = (LINES + ring_first - 1) % LINES;
  draw();
}

void key_handler(int key) {
  switch (key) {
  case 0x0d: /* Return */
    /* server に unsend_string を送信 */
    fprintf(server, "%s: %s\n", name, unsend_string);
  case 0x1b: /* Escape */
    str_index = 0;
    break;
  case 0x08: /* BackSpace */
    if (str_index > 0) str_index--;
    break;
  case 0xffe1: /* Shift */
	break;
  default:
    if (isascii(key))
      unsend_string[str_index++] = key;
  }
  unsend_string[str_index] = '\0';
  draw();
}

/* 接続が閉じたとき呼ばれる */
void close_handler(FILE* server) {
  puts("server closed");
  EzExitEventLoop();            /* プログラム終了 */
}

int main(int argc, char *argv[]) {
  char *server_address;
  if (argc < 3) return 1;
  name = argv[1];
  server_address = argv[2];
  EzSetReceiveHandler(receive_handler);
  EzSetConnectionCloseHandler(close_handler);
  EzSetKeyHandler(key_handler);
  /* server_address で動作して、PORT番のポートで
     接続を待ち受けているサーバーに接続する */
  server = EzConnect(server_address, PORT);
  /* もしサーバーが見つからなかったりなどしたら終了 */
  if (server == NULL) return 1;
  EzEventLoop();
  return 0;
}
Yuki DOI
E-mail: 413834 at m.mie-u.ac.jp

チュートリアルの目次

  1. 初歩的な使い方
  2. 複雑な図形描写
  3. イベントドリブン
  4. ダブルバッファリング
  5. 画像ファイルの表示
  6. リアルタイムなキー入力
  7. ネットワーク機能

Takahiro SASAKI
E-mail: sasaki at arch.info.mie-u.ac.jp