ネットワーク・プログラミング 非同期I/Oとスレッド同期制御
全体の位置づけ オペレーションシステム(Linux) ソケットプログラミング プロセス カーネルとシステムコール プロセス間通信 スレッド クライアントーサーバ 非同期I/Oとマルチタスク
1.1 サーバーの非同期I/O 非同期I/O シグナルSIGIOの設定 UDPEchoServeの動作 シグナル処理関数がエコー処理をする UDPEchoServer 非同期I/O シグナルSIGIOを使って、パケット受信をプロセスに通知する。 プロセスはシグナルを受信するまで別の処理が出来る。 シグナルSIGIOの設定 sigaction()を使って、シグナルハンドラを指定する。 fcntl()を使って、ソケットのフラグをFASYNC (非同期I/O)に設定する。 UDPEchoServeの動作 SIGIOのシグナルハンドラを設定し、エコー要求があるまで他の処理を行う。 エコー要求が到着するとSIGIOがプロセスに送信され、ハンドラが実行される。 ハンドラは、recvfrom()とsendto()を実行して受信したデータグラムをエコーバックする。 SIGIO handler UDPEchoClient カーネル main signal関数の高機能版 sigaction() Echo SIGIO recvfrom() sendto() Echo Hello SIGIO recvfrom() sendto() Hello
1.2 クライアントのタイムアウト処理 タイムアウト タイムアウトの実装 UDPEchoClientの動作 hello hello 1.2 クライアントのタイムアウト処理 タイムアウト パケットの受信がないことを知る方法。 今からT秒後までに受信がなければ、シグナル(SIGALRM)がカーネルからプロセスに送られる。イベントがあれば、シグナルによる通知はリセットされる。 タイムアウトの実装 ブロックする関数(例えば、recvfrom)を実行する前にalarm()を実行する。 unsigned int alarm(unsigned int secs) UDPEchoClientの動作 2秒間応答がないとSIGALRMシグナルが送信されハンドラが起動される。 ハンドラは再送回数を1増やす。 エコーメッセージを再送する。 再送回数が最大値に到達した場合、プログラムを終了する。そうでなければ再送する。 UDPEchoClient UDPEchoServer alarm() sendto() hello hello alarm() sendto() hello 2秒 メッセージ紛失 SIGALRMハンドラ タイムアウト tries++ alarm() sendto() hello メッセージ紛失 タイムアウト SIGALRMハンドラ tries++
1.3 UDPEchoClient Timeout.c #define TIMEOUT_SECS 2 /* タイムアウト秒数*/ … int tries=0; /* 再送回数初期化 */ void CatchAlarm(int ignored); /* SIGALRMハンドラ */ int main(int argc, char *argv[]) { struct sigaction myAction; /* ハンドラ設定に使用する */ /* UDPソケット作成 */ … /* alarm シグナルのハンドラの設定 */ myAction.sa_handler = CatchAlarm; /* ハンドラ実行中は他のシグナルをブロックする */ if (sigfillset(&myAction.sa_mask) < 0) DieWithError("sigfillset() failed"); myAction.sa_flags = 0; if (sigaction(SIGALRM, &myAction, 0) < 0) DieWithError("sigaction() failed for SIGALRM"); /* 文字をサーバへ送信 */ … /* 応答待ち */ fromSize = sizeof(fromAddr); alarm(TIMEOUT_SECS); /* タイムアウト設定 */ while ((respStringLen = recvfrom(sock, echoBuffer, ECHOMAX, 0, (struct sockaddr *) &fromAddr, &fromSize)) < 0) if (errno == EINTR) /* アラーム終了 */ { if (tries < MAXTRIES) /* 最大再送回数か? */ printf("timed out, %d more tries...\n", MAXTRIES-tries); if (sendto(sock, echoString, echoStringLen, 0, (struct sockaddr *)&echoServAddr, sizeof(echoServAddr)) != echoStringLen) DieWithError("sendto() failed"); alarm(TIMEOUT_SECS); } else DieWithError("No Response"); DieWithError("recvfrom() failed"); /* 正常な受信処理 */ alarm(0); … void CatchAlarm(int ignored) /* SIGALRMハンドラ */ tries += 1; タイムアウトが発生 errno.hで定義 された外部変数 タイマーの再セット シグナルSIGALRMのハンドラCatchAlarmを設定 再送回数オーバー タイムアウト 以外で受信失敗 エコー文字の送信 タイマーをリセット タイマーセット 再送回数を1増加
セマフォ値が0ならば、セマフォ値が増えるのを待って1減らす 2.スレッド 2.1 セマフォを使った同期制御 スレッド用のセマフォ 使い方はプロセス間通信のセマフォと同じ スレッドの数だけセマフォが必要。 同期の取り方 スレッド1はセマフォ1に1を足し、セマフォ2から1を引く。 スレッド2はセマフォ2に1を足し、セマフォ1から1を引く。 sem_init セマフォの初期化 インクルードファイル #include <semaphore.h> 書式 int sem_init(sem_t *sem, int pshared, unsigned int value); 戻値 成功時 0 失敗時 -1 sem セマフォ識別子 pshared 0: 現在のプロセス内のスレッド間だけで使用 0以外: 複数プロセス間で使う Value セマフォ値 スレッド1の状態 0: 実行 1: 待ち 1 セマフォ1 セマフォ2 スレッド1 +1 -1 待ち スレッド2 引けない ここから同時に動く sem_post セマフォ値を1増やす インクルードファイル #include <semaphore.h> 書式 int sem_post(sem_t *sem); 戻値 成功時 0 失敗時 -1 sem_wait セマフォ値を1減らす インクルードファイル #include <semaphore.h> 書式 int sem_wait(sem_t *sem); 戻値 つねに0を返す スレッド2の状態 0: 実行 1: 待ち セマフォ値が0ならば、セマフォ値が増えるのを待って1減らす
2.2 同期を取りながら文字を出力するプログラム 2.2 同期を取りながら文字を出力するプログラム Mainプログラム スレッドで実行する関数 Helloworld.c その1 helloworld.c その2 char word[]={"Hello World!\n"}; sem_t sem1,sem2; int main() { pthread_t thread1,thread2; void *print1(void *args); void *print2(void *args); sem_init(&sem1,0,0); sem_init(&sem2,0,0); pthread_create(&thread1, NULL, print1, NULL); pthread_create(&thread2, NULL, print2, NULL); pthread_join(thread1,NULL); pthread_join(thread2,NULL); return EXIT_SUCCESS; } void *print1(void *args) { int i; for (i=0; i<strlen(word); i++) { sem_post(&sem1); // sem1 increment sem_wait(&sem2); // sem2 decrement printf("%c",word[i]); fflush(stdout); sleep(1); } return NULL; void *print2(void *args) { sem_post(&sem2); // sem2 increment sem_wait(&sem1); // sem1 decrement 同期ポイント 直ぐに出力 初期化 特別な設定をしなくてもword[]をアクセス出来る!! 同期ポイント oida@rpc261 soft2]$ gcc helloworld.c -o helloworld -lpthread [oida@rpc261 soft2]$ ./helloworld HHeelllloo WWoorrlldd!!
W(7番目の文字)で同期を取るthread1 宿題13 次のインクルードファイルが必要 #include <pthread.h> #include <semaphore.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> (helloworld.cを参考にして)「Hello World!」の「W」で( 「W」以降ではない)同期をとって印字するプログラムhw13.cを作成せよ。但し、二つのスレッドは1秒毎と7秒毎に1文字印字する。 表紙に氏名と学籍番号を書く 本文にプログラムを書く プログラムの実行結果を書く 実施した内容を説明する文章を書く レポートの締切は次の週の水曜日18:00 W(7番目の文字)で同期を取るthread1 void *print1(void *args) { int i; for (i=0; i<strlen(word); i++) { if (i==6) { sem_post(&sem1); // sem1 increment sem_wait(&sem2); // sem2 decrement } printf("%c",word[i]); fflush(stdout); sleep(1); return NULL; thread2では、sleep(7);にする [oida@rpc261 soft2]$ gcc hw13.c -o hw13 -lpthread [oida@rpc261 soft2]$ ./hw13 HHello ello WWorld! orld! [oida@rpc261 soft2]$