ネットワークプログラミングの続き 前回はチャットを行うプログラムを作成し、ネットワークを利用したプログラミングの基本につ いて学んだ。本日は、前回作成したプログラムを改良していく。具体的には、以下の2つの項目に ついて習っていく。 ・ホスト名や IP アドレスの取得の方法 ・fork()システムコールを使い、子プロセスを作成する方法 チャットプログラムの改良 前回のプログラムを以下のように改良していく。太字部分が変更部分である。また、変更の大部 分はサーバープログラムについてであり、以下のような機能を追加している。 ・自分のホスト名と IP アドレスを表示する。これは gethostname()と gethostbyname() システムコールを使う。 ・複数のクライアントからのメッセージを表示する。これは fork()システムコールを使 って、子プロセスを生成して、複数のクライアントに対応する。 ※現在のシステムではサーバープログラムの IP アドレスはローカルループバック(127.0.0.1)になる。 クライアントプログラム e_client.c #include <stdio.h> #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)); // アドレス構造体の初期化
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); // この 2 行の順番を if(strncmp(message, "exit", 4) == 0) break; // 入れ替え
} close(soc); return 0; } サーバープログラム e_server.c #include <stdio.h> #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); // 自ホスト情報の取得
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); // 受け付けるコネクションの最大数を 5 に設定 signal(SIGINT, close_process); // [Ctrl]+c を押したとき signal(SIGCHLD, kill_zombie_process); // 子プロセスの監視 // メッセージを受ける 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; }
// ゾンビプロセスを終了させる 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()を実行すると呼び出した親プロセスとほぼ同様のプロセスがコピーされる。例えば、 呼び出す前に変数に代入した値があったとすれば、それもコピーされる。いくつかの違いもあるが、 現状で知っておくべきことは以下である。
・PID(プロセス ID):プロセスを区別する番号 ・PPID(親プロセス ID):自分を呼び出した親プロセスのプロセス ID プロセス ID についてはターミナル上で「ps」とコマンドを実行すると確認することができる。 子プロセスは同じプログラムではあるが、データ空間が異なるため、親と子では異なる動作をさ せることが出来る。親と子のプロセスの区別は fork()の戻り値で区別できる。親プロセスの場合 の fork()の戻り値は子プロセスのプロセス ID であり、正の整数となる。子プロセスについては全 て 0 となる。子プロセスの生成に失敗した場合には戻り値が-1 となる。次に簡単な子プロセスを 使ったプログラムを示す。 マルチプロセス用プログラム fork.c #include <stdio.h>
#include <stdlib.h> // exit()を使うため #include <sys/types.h> // fork()を使うため #include <unistd.h> // fork()を使うため
int sum = 3; int main(void) { int pid; pid = 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 にそれぞれ加算を行った結果となり、グローバル変数も含めて、別の データ空間が用意されていることが確認できる。
演習 クライアントとサーバーのプログラムを実行し、動作を理解しなさい。 課題8 課題8-1 サーバーのプログラムから自分のホスト名と IP アドレスを表示するプログラムを作 成しなさい。ただし通信の部分を含め、余計な処理を全て除くこと。この場合に必要となるヘッダ ファイルは以下だけである。 #include <stdio.h> #include <string.h> #include <unistd.h> #include <netdb.h> 課題8-2 子プロセスを生成するプログラムにおいて、次の動作を行うプログラムを作成しなさ い。 ・親プロセスにおいて、6+2 を計算して、結果を表示 ・子プロセスにおいて、6-2 を計算して、結果を表示
課題8-3 gethostbyname() 関数を使って、www.akita-nct.jp のIPアドレスを表示するプログ ラムを作成しなさい。
参考文献 このテキストは以下の文献を参考としました。