ネットワークプログラミングの続き前回はチャットを行うプログラムを作成し ネットワークを利用したプログラミングの基本について学んだ 本日は 前回作成したプログラムを改良していく 具体的には 以下の2つの項目について習っていく ホスト名や IP アドレスの取得の方法 fork() システムコールを使い 子プロセスを作成する方法 チャットプログラムの改良 前回のプログラムを以下のように改良していく 太字部分が変更部分である また 変更の大部 分はサーバープログラムについてであり 以下のような機能を追加している 自分のホスト名と IP アドレスを表示する これは gethostname() と gethostbyname() システムコールを使う 複数のクライアントからのメッセージを表示する これは fork() システムコールを使って 子プロセスを生成して 複数のクライアントに対応する 現在のシステムではサーバープログラムの IP アドレスはローカルループバック (127.0.0.1) になる クライアントプログラム e_client.c #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 5320 int main(int argc, char *argv[]) int soc; char message[80], ip_address[16]; struct sockaddr_in server; if(argc == 1) printf(" 引数に IP アドレスが必要です \n"); exit(0); strcpy(ip_address, argv[1]); // コマンドライン引数の IP アドレスをコピー soc = socket(pf_inet, SOCK_STREAM, 0); // ソケットの作成 memset((char *)&server, 0, sizeof(server)); // アドレス構造体の初期化 // アドレス構造体のメンバに値を設定 - 1 -
server.sin_family = AF_INET; server.sin_port = htons(port); server.sin_addr.s_addr = inet_addr(ip_address); connect(soc, (struct sockaddr *)&server, sizeof(server)); // メッセージを送る while(1) printf("input message:"); fgets(message, 80, stdin); send(soc, message, strlen(message), 0); if(strncmp(message, "exit", 4) == 0) break; // 接続要求 // この 2 行の順番を // 入れ替え close(soc); return 0; サーバープログラム e_server.c #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h> #include <unistd.h> #include <signal.h> #include <netdb.h> #define PORT 5320 void kill_zombie_process(int sig); void close_process(int unused); char *show_ip(char *ip_address); int main(void) int soc, acc, size; char buffer[80]; struct sockaddr_in client, server; struct hostent *server_host; pid_t pid; char host_name[257]; int temp; // --- 自ホスト情報の取得と表示 ----- memset(host_name, 0, sizeof(host_name)); gethostname(host_name, 256); server_host = gethostbyname(host_name); // 自ホスト名の取得 // 自ホスト情報の取得 - 2 -
printf("\n-------- informations of server ----------\n"); printf("host name:%s\n", host_name); // 自ホスト名の表示 printf("ip = %s\n", show_ip(server_host->h_addr)); // IP アドレスを表示 printf("\n\n"); // ---------------------------------- soc = socket(pf_inet, SOCK_STREAM, 0); memset((char *)&server, 0, sizeof(server)); // ソケットの作成 // アドレス構造体の初期化 // アドレス構造体のメンバに値を設定 server.sin_family = AF_INET; server.sin_addr.s_addr = htonl(inaddr_any); server.sin_port = htons(port); bind(soc, (struct sockaddr *)&server, sizeof(server)); size = sizeof(client); listen(soc, 5); signal(sigint, close_process); signal(sigchld, kill_zombie_process); // 受け付けるコネクションの最大数を 5 に設定 // [Ctrl]+c を押したとき // 子プロセスの監視 // メッセージを受ける while(1) acc = accept(soc, (struct sockaddr *)&client, &size); pid = fork(); // 接続許可 if(pid == 0) //----- 子プロセス -------------- close(soc); // ソケットをクローズ while(1) memset(buffer, '\0', sizeof(buffer));// 文字列の終わりを示す '\0' で埋める recv(acc, buffer, 80, 0); // データの受信 // クライアントアドレス表示 printf("%s> ", show_ip((char *)&client.sin_addr)); printf("%s", buffer); // 受信データを表示 if(strncmp(buffer, "exit", 4) == 0) break; // "exit" ならば, ループ脱出 close(acc); // ファイルディスクリプタをクローズ exit(0); // 子プロセス終了 else //----- 親プロセス -------------- close(acc); // ファイルディスクリプタをクローズ return 0; - 3 -
// ゾンビプロセスを終了させる void kill_zombie_process(int sig) while(waitpid(-1, NULL, WNOHANG) > 0); signal(sigchld, kill_zombie_process); // [Ctrl]+c が押されたときの処理 ( 子プロセスの終了確認は未対応 ) void close_process(int unused) exit(0); // IP アドレスのポインターを返す char *show_ip(char *ip_address) static char ip[7]; char ipnum[4]; // 静的変数. メモリーから消えない bcopy(ip_address, ipnum, 4); sprintf(ip, "%u.%u.%u.%u", // %u: 符号なし 10 進数 (unsigned char)ipnum[0], (unsigned char)ipnum[1], (unsigned char)ipnum[2], (unsigned char)ipnum[3]); return ip; これらのプログラムについては WEB ページの ネットワークプログラミング を参考にしている URL は http://cai.int-univ.com/sugsi/lecture/netprog/index.html である 自分のホスト名を取得する自分の PC に設定されているホスト名を取得するため gethostname() システムコールを使う これで獲得したホスト名を用いてホスト情報を得るため gethosotbyname() を使う これらの詳細については man コマンドを用いて man gethostname man gethosobyname で調べておくこと 子プロセスの生成マルチプロセスについては教科書 p.308-312 に詳しく書いてある ここでは概要について述べる 実行中のプログラムはプロセスと呼ばれ 一つのプログラム中で複数のプロセスが実行されるものをマルチプロセスと呼ぶ マルチプロセスではプロセスが別のプロセスを呼び出すことで実現され 呼び出すものと呼び出されるものには親子関係で区別される 呼び出す側のプロセスを親プロセスといい 呼び出された側のプロセスを子プロセスという linux(unix) プログラミングではマルチプロセスを実現する fork() システムコールが用意されている fork() を実行すると呼び出した親プロセスとほぼ同様のプロセスがコピーされる 例えば 呼び出す前に変数に代入した値があったとすれば それもコピーされる いくつかの違いもあるが 現状で知っておくべきことは以下である - 4 -
PID( プロセス ID): プロセスを区別する番号 PPID( 親プロセス ID): 自分を呼び出した親プロセスのプロセス ID プロセス ID についてはターミナル上で ps とコマンドを実行すると確認することができる 子プロセスは同じプログラムではあるが データ空間が異なるため 親と子では異なる動作をさせることが出来る 親と子のプロセスの区別は fork() の戻り値で区別できる 親プロセスの場合の fork() の戻り値は子プロセスのプロセス ID であり 正の整数となる 子プロセスについては全て 0 となる 子プロセスの生成に失敗した場合には戻り値が-1 となる 次に簡単な子プロセスを使ったプログラムを示す マルチプロセス用プログラム fork.c #include <stdlib.h> #include <sys/types.h> #include <unistd.h> int sum = 3; int main(void) int pid; pid = fork(); // exit() を使うため // fork() を使うため // fork() を使うため if(pid == 0) // -------- 子プロセス ------------- printf(" 子プロセス \n"); sum += 8; printf(" 合計 = %d\n", sum); else // -------- 親プロセス ------------- printf(" 親プロセス \n"); sum += 3; printf(" 合計 = %d\n", sum); return 0; 出力結果子プロセス合計 = 11 親プロセス合計 = 6 このプログラムではグローバル変数に加算して代入する処理を親 子プロセスそれぞれで行って いる 出力結果は初期値の 3 にそれぞれ加算を行った結果となり グローバル変数も含めて 別の データ空間が用意されていることが確認できる - 5 -
演習クライアントとサーバーのプログラムを実行し 動作を理解しなさい 課題 8 課題 8-1 サーバーのプログラムから自分のホスト名と IP アドレスを表示するプログラムを作成しなさい ただし通信の部分を含め 余計な処理を全て除くこと この場合に必要となるヘッダファイルは以下だけである #include <string.h> #include <unistd.h> #include <netdb.h> 課題 8-2 子プロセスを生成するプログラムにおいて 次の動作を行うプログラムを作成しなさい 親プロセスにおいて 6+2 を計算して 結果を表示 子プロセスにおいて 6-2 を計算して 結果を表示 課題 8-3 gethostbyname() 関数を使って www.akita-nct.jp の IP アドレスを表示するプログ ラムを作成しなさい 参考文献このテキストは以下の文献を参考としました [1] C 言語によるプログラミング応用編第 2 版 内田智史監修 オーム社 - 6 -