サンプルプログラム(1)のサーバプログラム(server.c)はinetdか
ら起動するものであるが、inetdを使用せずに同じ動作をするデーモン
型のサーバプログラムを作成し、実行結果を示すとともに、inetdを使
用するサーバプログラムとそうでないものとの実装上の違いを説明せ
よ。
myserver.cのソース
1 /*** TCP Server for inetd ***/
2
3 #include <stdio.h>
4 #include <sys/types.h>
5 #include <sys/errno.h>
6 #include <string.h>
7 #include <ctype.h>
8 #include <sys/socket.h>
9 #include <netinet/in.h>
10 #include <netdb.h>
11
12 #define DATAFILE "/Users/j04009/slab2/data"
13 #define DELIMITS " \t\n\r"
14
15 char datavalue[256];
16
17
18 int GetLineFromPeer(int new_sockfd, char *line)
19 {
20 int i;
21
22 for(i = 0 ; ; )
23 {
24 if(1 != read(new_sockfd,line + i,1))
25 continue;
26 else
27 {
28 i++;
29 *(line + i) = (char)0;
30 }
31
32 if(((char)('\n')) == (*(line + i - 1)))
33 return i;
34 }
35 }
36
37 char *GetKeywordData(char *key)
38 {
39 FILE *fp;
40 char buff[256];
41
42 if((FILE *)NULL == (fp = fopen(DATAFILE,"r")))
43 return (char *)NULL;
44
45 for(;;)
46 {
47 char *p;
48 char *pp;
49
50 fgets(buff,256,fp);
51
52 if(feof(fp))
53 break;
54
55 if(ferror(fp))
56 break;
57
58 if((char *)NULL == (p = strtok(buff,DELIMITS)))
59 continue;
60
61 if((char *)NULL == (pp = strtok((char *)NULL,DELIMITS)))
62 continue;
63
64 if(0 == strcmp(p,key))
65 {
66 fclose(fp);
67 strcpy(datavalue,pp);
68 return datavalue;
69 }
70 }
71
72 fclose(fp);
73 return (char *)NULL;
74 }
75
76
77 int main()
78 {
79 int sockfd;
80 int new_sockfd;
81 int writer_len;
82 struct sockaddr_in reader_addr;
83 struct sockaddr_in writer_addr;
84
85 /*ソケットの生成*/
86 if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
87 perror("reader:socket");
88 exit(1);
89 }
90
91 /*通信ポート・アドレスの設定*/
92 bzero((char *) &reader_addr, sizeof(reader_addr));
93 reader_addr.sin_family = AF_INET;
94 reader_addr.sin_addr.s_addr = htonl(INADDR_ANY);
95 reader_addr.sin_port = htons(5682);
96
97 /*ソケットにアドレスを結びつける*/
98 if(bind(sockfd, (struct sockaddr *)&reader_addr, sizeof(reader_addr)) < 0){
99 perror("reader:bind");
100 exit(1);
101 }
102
103 /*コネクト要求をいくつまで待つかを設定*/
104 if(listen(sockfd,5) < 0){
105 perror("reader:listen");
106 close(sockfd);
107 exit(1);
108 }
109
110 char RecvBuff[256];
111
112 for(;;) /*デーモンとして動かすのでエラー以外でプログラムを終了させる命令を消した*/
113 {
114 int flag = 1; /*下にあるwhile文の抜けるために使う*/
115
116 /*コネクト要求を待つ*/
117 if((new_sockfd = accept(sockfd,(struct sockaddr *)&writer_addr, &writer_len)) < 0){
118 perror("reader:accept");
119 exit(1);
120 }else{
121 while(flag)
122 {
123 char *p;
124 char SendBuff[256];
125 char *pp;
126
127 (void)GetLineFromPeer(new_sockfd, RecvBuff); /* Get 1 line from peer */
128
129 if(((char)('=')) == *(RecvBuff)) /* クライアントが接続を切ったときに動く */
130 {
131 close(new_sockfd); /*ソケットを廃棄*/
132 flag = 0; /*flagを偽(0)とする*/
133 break; /*このときflagの値によりwhileを抜ける*/
134 }else{
135 if((char *)NULL != (p = strchr(RecvBuff,'=')))
136 {
137 *p = (char)0;
138
139 if((char *)NULL == (pp = GetKeywordData(RecvBuff)))
140 sprintf(SendBuff,"%s=\n",RecvBuff);
141 else
142 sprintf(SendBuff,"%s=%s\n",RecvBuff,pp);
143
144 (void)write(new_sockfd,SendBuff,strlen(SendBuff));
145 }
146 }
147 }
148 }
149 }
150 }
myclient.cのソース
ここは課題2で使うclient.cと変わらないのでソースは省略する.
実行結果
[nw0409:~/slab2] j04009% ps aux | grep inetd
j04009 1718 0.0 0.0 8860 8 std R+ 4:58PM 0:00.00 grep inetd
[nw0409:~/slab2] j04009% ./myserver &
[1] 1719
[nw0409:~/slab2] j04009% ps | grep myserver
1719 std S 0:00.01 ./myserver
1721 std R+ 0:00.00 grep myserver
[nw0409:~/slab2] j04009% ./myclient
Connected.
Input Keyword = warning: this program uses gets(), which is unsafe.
123
Keyword = [123] / Data = [456]
Input Keyword = shiro
Keyword = [shiro] / Data = [kuro]
Input Keyword = abc
Keyword = [abc] / Data = []
Input Keyword =
Disocnnected.
[nw0409:~/slab2] j04009% ps | grep myserver
1719 std S 0:00.01 ./myserver
1724 std R+ 0:00.00 grep myserver
[nw0409:~/slab2] j04009%
実行結果の説明
まず,inetdが動いてないことを確認してからmyserverをバックグラウンドで
実行する.そして,その後myclientを実行した後,myserverがまだ起動
していることを確認した.
このことから,このmyserverプログラムは,デーモン型のサーバプログラムと
いえる.
実装上の違い
inetdは,1つのプロセスで複数のポートを監視しており,クライアントからの
接続要求がきた場合に初めてサーバプログラムを実行する.この役割からスー
パーサーバとも呼ばれる.
inetdは,socket,bind,listen,accept,close などの接続作業をす
べてやってくれる.そのため,inetdとサーバプログラムは標準入出力を使ってク
ライアントプログラムと通信できるので,サーバプログラムは標準入出力を使
うプログラムだけで良い.
一方,inetdを使わないサーバプログラムは,socket,bind,listen,accept,
close を実装しなければならない.それに,サーバプログラムをデーモン
として動かしておく必要がある.
サンプル(server.c)に追加した部分の説明
socket()
これは,85行目にある.
socket()には引数が3つあり、第一引数はプロトコルファミリー,第二引数はソケッ
トの通信方式,第三引数はソケットに使用される固有のプロトコルを指定
する.
myserver.cの設定
- AF_INET
IPv4インターネットプロトコル
- SOCK_STREAM
順序性と信頼性がある双方向のコネクション通信
- 0
プロトコルが一つの場合は0としてよい.0とすることで,使用するプロトコルが
上の二つの引数の組み合わせから自動的に設定される.
これでTCP/IPプロトコルで使用できるソケット記述子を返す.それはソケット
に関連するシステムコールで使用される.
socket()が失敗すると -1 を返して終了.
bind()
これは,bind()の設定は92行目からである.
はじめにsockaddr_in構造体の中身をbzero()関数を使って0に埋める(初期化する)。
次にsockaddr_in構造体のメンバ変数を設定していく.
myserver.cの設定
- sin_family
プロトコルファミリAF_INET(socketと同じ).
- sin_addr.s_addr
htol()を通じてINADDR_ANY(どのホストでも接続可)というアドレス
- sin_port
htons()を使って,通信に利用するポート番号
htonlは32ビット,htonsは16ビットのホストバイトオーダをネットワークバ イトオーダに変換する.これはネットワークに流すデータ形式がビッグエン ディアンということが決まっているからである.
bind()には引数が3つあり、第一引数はソケット記述子,第二引数
はソケットのアドレスや ポート番号を定義するソケットアドレス
構造体へのポ インタ,第三引数は.ソケットアドレス構造体のサ
イズを指定する.
myserver.cの設定
- sockfd
ソケット記述子が割り振られた変数
- (struct sockaddr *)&reader_addr
sockaddr_in型のポインタをsockaddr型のポインタに変換. こうすることでさまざまなプロトコルの違いを吸収できる.
- sizeof(reader_addr)
構造体の大きさ
これでソケット記述子に構造体で指定されたアドレスやポート番号
が割り振られる.
listen()
これは,104行目にある.
第一引数は接続準備する対象となるソケット記述子.第二引
数は待ち行列の最大長を指定する.
myserver.cの設定
- sockfd
接続準備する対象のソケット記述子(socketで作ったもの)
- 5
接続要求のための待ち行列の最大数を5に指定.
複数の接続要求が来るような場
合,サーバが何らかの処理をしていると接続要求を失う可能性があ
る.処理しきれない接続要求を一時的に待ち状態にしておく必要が
あり,その最大数が第二引数となる.ただし,その最大数
はオペレーティングシステムに制限され,5以下になることが多い
ため,第二引数は5とした.
accept()
これは,117行目にある.
接続要求の待機はaccept()システムコールを使用する.accept()は接続要求
があるまで終了しない.つまり,サーバは accept()をを実行した時点で,
一時停止し,接続要求があると,accept()直後から再開する.
第一引数sockfdはソケット番号,第二引数addrはsockaddr構造体への
ポインタである.第三引数には addr のサイズを格納する数値へ
のポインタを指定する.
myserver.cの設定
- sockfd
ソケット記述子(socketで作ったもの)
- ,(struct sockaddr *)&writer_addr
bind()の所の 「(struct sockaddr *)&reader_addr」と同じ働き.
- &writer_len
bind()の所のと sizeof(reader_addr)と同じ働き.
accept()はクライアントから接続要求が来ると,接続処理を行うと同時に新
しいソケットを生成する.この新しいソケットはその接続に使われるソケッ
トであり,元々のソケットは次の接続要求の受け付けのために使われる.
なお,返値はその接続要求のあったソケットの番号(新しく作成されたソケッ
ト番号)となる.失敗した場合は-1を返す.
close()
close引数はクローズするソケットの番号を指定する.クローズに
失敗した場合は-1を返す.