| 前へ << C 言語で echo サーバを作ってみよう (1) | C言語で ftp クライアントを作ってみよう (1) >> 次へ |
117: int
118: main(){
119: fd_set target_fds;
120: fd_set org_target_fds;
121: int sock_optval = 1;
122: int port = 5000;
123: /* リスニングソケットを作成 */
124: listening_socket = socket(AF_INET, SOCK_STREAM, 0);
125:
126: /* ソケットオプション設定 */
127: if ( setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR,
128: &sock_optval, sizeof(sock_optval)) == -1 ){
129: perror("setsockopt");
130: exit(1);
131: }
132: /* アドレスファミリ・ポート番号・IPアドレス設定 */
133: sin.sin_family = AF_INET;
134: sin.sin_port = htons(port);
135: sin.sin_addr.s_addr = htonl(INADDR_ANY);
136:
137: if ( bind(listening_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0 ){
138: perror("bind");
139: exit(1);
140: }
141:
142: if ( listen(listening_socket, SOMAXCONN) == -1 ){
143: perror("listen");
144: exit(1);
145: }
146: printf("ポート %d を見張ります。\n", port);
まずは main 関数から。これまでのように
119: fd_set target_fds; 120: fd_set org_target_fds;fd_set というのは select(2) に渡す構造体です。 どのディスクリプタを監視対象にするかを指定します。 select(2) は渡された fd_set 構造体を書き換えてしまうので、 org_target_fds にデータをセットし、select(2) を呼ぶ前に org_target_fds を target_fds にコピーすることにします。
148: /* 監視対象のディスクリプタ一覧をゼロクリア */ 149: FD_ZERO(&org_target_fds); 150: /* リスニングソケットを監視対象に追加 */ 151: FD_SET(listening_socket, &org_target_fds);まずは FD_ZERO でゼロクリアします。 これによりいずれのディスクリプタも監視対象にしないことになります。 FD_ZERO は FreeBSD 4.2R では /usr/include/sys/types.h で
#define FD_ZERO(p) bzero(p, sizeof(*(p)))と定義されているマクロです。次に FD_SET で リスニングソケットのビットを立てます。これにより リスニングソケットが select(2) の監視対象となります。
153: while (1){
154: int i;
155: time_t now_time;
156: struct timeval waitval; /* select に待ち時間を指定するための構造体 */
157: waitval.tv_sec = 2; /* 待ち時間に 2.500 秒を指定 */
158: waitval.tv_usec = 500;
159:
160: /* org_target_fds を target_fds にコピー */
161: memcpy(&target_fds, &org_target_fds, sizeof(org_target_fds));
162:
163: select(FD_SETSIZE, &target_fds, NULL, NULL, &waitval);
waitval に 2.500 を指定します。select(2) を呼んで、
どのソケットも読み出し可能にならない場合は、2.5 秒経過すると
select(2) から戻ることになります。
memcpy(3) で org_target_fds から target_fds にコピーし、 select(2) を呼びます。select(2) の第一引数には何個分のディスクリプタを 監視対象にするかを指定します。例えば第一引数に 5 を指定すると、 ディスクリプタ 0番〜4番の 5個のディスクリプタ (なおかつ FD_SET で 監視対象に指定されているもの) についてチェックが行われます。
ここでは FD_SETSIZE を指定していますが、これは多くの環境では
#define FD_SETSIZE 1024となっています。これは select(2) が扱えるディスクリプタの最大数です。 これでは毎回 0〜1023 番のディスクリプタ (そのうちFD_SET されているもの) が チェックされます。CPU 資源を無駄使いになりますので、自前で最大数を 指定するのがよいのですが、ここではサンプルということで手抜きをしています。
なお、select(2) を呼ぶと waitval と target_fds が 更新されてしまうので、毎ループごとに値をセットし直す必要があります。
166: for ( i=0 ; i<FD_SETSIZE ; i++ ){
167: if ( FD_ISSET(i, &target_fds) ){
168: printf("ディスクリプタ %d 番が読み込み可能です。\n", i);
169:
170: if ( i == listening_socket ){
171: int new_sock;
172: /* 新しいクライアントがやってきた */
173: new_sock = accept_new_client(i);
174: if ( new_sock != -1 ){
175: /* 監視対象に新たなソケットを追加 */
176: FD_SET(new_sock, &org_target_fds);
177: }
178: } else {
179: int read_size;
180: /* 接続済みソケットからデータが送信されてきた */
181: read_size = read_and_reply(i);
182:
183: if ( read_size == -1 || read_size == 0 ){
184: /* 切断したソケットを監視対象から削除 */
185: FD_CLR(i, &org_target_fds);
186: }
187: }
188: }
189: }
各ディスクリプタに対して FD_ISSET を使って
読み込みが可能かどうかを調べます。FD_ISSET が真を返せば
読み込み可能であることがわかります。
ディスクリプタがリスニングソケットであれば、
29: /*-----------------------------------------------------
30: 引数でリスニングソケットを受け取り、accept し、
31: client_info に新しいクライアントの情報を登録する。
32: 戻り値は新しいクライアントのソケットディスクリプタ。
33: ただしエラー発生時は -1 を返す。
34: -----------------------------------------------------*/
35: int
36: accept_new_client(int sock){
37: int len;
38: int new_socket;
39: struct hostent *peer_host;
40: struct sockaddr_in peer_sin;
41:
42: len = sizeof(sin);
43: new_socket = accept(listening_socket, (struct sockaddr *)&sin, &len);
44:
45: if ( new_socket == -1 ){
46: perror("accept");
47: exit(1);
48: }
49:
50: if ( new_socket > FD_SETSIZE-1 ){
51: return -1;
52: }
53: /* ここから先はデバッグ用の情報取得 */
54: len = sizeof(peer_sin);
55: getpeername(new_socket,
56: (struct sockaddr *)&peer_sin, &len);
57:
58: peer_host = gethostbyaddr((char *)&peer_sin.sin_addr.s_addr,
59: sizeof(peer_sin.sin_addr), AF_INET);
60:
61: /* ホスト名 */
62: strncpy(client_info[new_socket].hostname, peer_host->h_name,
63: sizeof client_info[new_socket].hostname);
64: /* IP アドレス */
65: strncpy(client_info[new_socket].ipaddr, inet_ntoa(peer_sin.sin_addr),
66: sizeof client_info[new_socket].ipaddr);
67: /* ポート番号 */
68: client_info[new_socket].port = ntohs(peer_sin.sin_port);
69: /* 現在時刻を最終アクセス時刻として記録しておく */
70: time(&client_info[new_socket].last_access);
71:
72: printf("接続: %s (%s) ポート %d ディスクリプタ %d 番\n",
73: client_info[new_socket].hostname,
74: client_info[new_socket].ipaddr,
75: client_info[new_socket].port,
76: new_socket);
77: return new_socket;
78: }
81: /*-----------------------------------------------------
82: 引数でソケットディスクリプタを受け取り、そのソケットから
83: read(2) で文字列を読み込み、文字列をそのままクライアントに
84: 送信する。read(2) の戻り値をそのまま返す。
85: -----------------------------------------------------*/
86: int
87: read_and_reply(int sock){
88: int read_size;
89: char buf[BUF_LEN];
90:
91: read_size = read(sock, buf, sizeof(buf)-1);
92:
93: if ( read_size == 0 || read_size == -1 ){
94: printf("%s (%s) ポート %d ディスクリプタ %d 番からの接続が切れました。\n",
95: client_info[sock].hostname,
96: client_info[sock].ipaddr,
97: client_info[sock].port,
98: sock);
99: close(sock);
100: client_info[sock].last_access = 0;
101: } else {
102: /* 文字列終端を \0 で terminate */
103: buf[read_size] = '\0';
104: printf("%s (%s) ポート %d ディスクリプタ %d 番からのメッセージ: %s",
105: client_info[sock].hostname,
106: client_info[sock].ipaddr,
107: client_info[sock].port,
108: sock,
109: buf);
110: write(sock, buf, strlen(buf));
111: time(&client_info[sock].last_access);
112: }
113: return read_size;
114: }
この下請け関数の中では、デバッグ表示用にクライアントの情報を保持しておく以下のような
構造体を使用しています。
17: typedef struct CLIENT_INFO {
18: char hostname[BUF_LEN]; /* ホスト名 */
19: char ipaddr[BUF_LEN]; /* IP アドレス */
20: int port; /* ポート番号 */
21: time_t last_access; /* 最終アクセス時刻 */
22: } CLIENT_INFO;
23:
24: CLIENT_INFO client_info[FD_SETSIZE];
例えばディスクリプタ4番の情報は
下請け関数 accept_new_client、read_and_reply で 最終アクセス時刻を client_info に記録しておきます。
70: time(&client_info[new_socket].last_access);
111: time(&client_info[sock].last_access);そして select(2) から戻った後、全ソケットの最終更新時刻をチェックします。
191: time(&now_time); /* 現在時刻を取得 */
192:
193: for ( i=0 ; i<FD_SETSIZE ; i++ ){
194: /* 監視対象でないソケットはスキップ */
195: if ( ! FD_ISSET(i, &org_target_fds) ) continue;
196: /* リスニングソケットはスキップ */
197: if ( i == listening_socket ) continue;
198:
199: if ( now_time-10 > client_info[i].last_access ){
200: printf("%s (%s) ポート %d ディスクリプタ %d 番から10秒以上アクセスがありません。切断します。\n",
201: client_info[i].hostname,
202: client_info[i].ipaddr,
203: client_info[i].port,
204: i);
205: close(i);
206: /* 切断したソケットを監視対象から削除 */
207: FD_CLR(i, &org_target_fds);
208: }
209: }
FD_ISSET を使って監視対象でないソケットはスキップします。
また、リスニングソケットはタイムアウトの対象外にしたいので、これも
スキップします。
そして最終アクセス時刻 client_info[ディスクリプタ].last_access が
現在時刻より10秒以上前ならタイムアウトしたものとみなして、
close(2) してソケットをクローズし、FD_CLR で監視対象から外します。
ポート 5000 を見張ります。 新たなクライアント (=クライアント A) が接続してきた ディスクリプタ 3 番が読み込み可能です。 接続: localhost (127.0.0.1) ポート 2716 ディスクリプタ 4 番 クライアント A からのメッセージ ディスクリプタ 4 番が読み込み可能です。 localhost (127.0.0.1) ポート 2716 ディスクリプタ 4 番からのメッセージ: I'm client A. 新たなクライアント (=クライアント B) が接続してきた ディスクリプタ 3 番が読み込み可能です。 接続: localhost (127.0.0.1) ポート 2718 ディスクリプタ 5 番 クライアント A からのメッセージ ディスクリプタ 4 番が読み込み可能です。 localhost (127.0.0.1) ポート 2716 ディスクリプタ 4 番からのメッセージ: Hello.. クライアント B からのメッセージ ディスクリプタ 5 番が読み込み可能です。 localhost (127.0.0.1) ポート 2718 ディスクリプタ 5 番からのメッセージ: I'm client B. クライアント A がタイムアウト localhost (127.0.0.1) ポート 2716 ディスクリプタ 4 番から10秒以上アクセスがありません。切断します。 クライアント B がコネクション切断 ディスクリプタ 5 番が読み込み可能です。 localhost (127.0.0.1) ポート 2718 ディスクリプタ 5 番からの接続が切れました。
1: /* $Id: echo-server-select.c,v 1.2 2005/06/11 20:25:10 68user Exp $ */
2:
3: #include <stdio.h>
4: #include <sys/types.h>
5: #include <sys/socket.h>
6: #include <time.h>
7: #include <netdb.h>
8: #include <string.h>
9: #include <sys/time.h>
10: #include <unistd.h>
11: #include <netinet/in.h>
12: #include <arpa/inet.h>
13:
14: #define BUF_LEN 256 /* バッファのサイズ */
15:
16: /* クライアントの情報を保持する構造体 */
17: typedef struct CLIENT_INFO {
18: char hostname[BUF_LEN]; /* ホスト名 */
19: char ipaddr[BUF_LEN]; /* IP アドレス */
20: int port; /* ポート番号 */
21: time_t last_access; /* 最終アクセス時刻 */
22: } CLIENT_INFO;
23:
24: CLIENT_INFO client_info[FD_SETSIZE];
25:
26: int listening_socket;
27: struct sockaddr_in sin;
28:
29: /*-----------------------------------------------------
30: 引数でリスニングソケットを受け取り、accept し、
31: client_info に新しいクライアントの情報を登録する。
32: 戻り値は新しいクライアントのソケットディスクリプタ。
33: ただしエラー発生時は -1 を返す。
34: -----------------------------------------------------*/
35: int
36: accept_new_client(int sock){
37: int len;
38: int new_socket;
39: struct hostent *peer_host;
40: struct sockaddr_in peer_sin;
41:
42: len = sizeof(sin);
43: new_socket = accept(listening_socket, (struct sockaddr *)&sin, &len);
44:
45: if ( new_socket == -1 ){
46: perror("accept");
47: exit(1);
48: }
49:
50: if ( new_socket > FD_SETSIZE-1 ){
51: return -1;
52: }
53: /* ここから先はデバッグ用の情報取得 */
54: len = sizeof(peer_sin);
55: getpeername(new_socket,
56: (struct sockaddr *)&peer_sin, &len);
57:
58: peer_host = gethostbyaddr((char *)&peer_sin.sin_addr.s_addr,
59: sizeof(peer_sin.sin_addr), AF_INET);
60:
61: /* ホスト名 */
62: strncpy(client_info[new_socket].hostname, peer_host->h_name,
63: sizeof client_info[new_socket].hostname);
64: /* IP アドレス */
65: strncpy(client_info[new_socket].ipaddr, inet_ntoa(peer_sin.sin_addr),
66: sizeof client_info[new_socket].ipaddr);
67: /* ポート番号 */
68: client_info[new_socket].port = ntohs(peer_sin.sin_port);
69: /* 現在時刻を最終アクセス時刻として記録しておく */
70: time(&client_info[new_socket].last_access);
71:
72: printf("接続: %s (%s) ポート %d ディスクリプタ %d 番\n",
73: client_info[new_socket].hostname,
74: client_info[new_socket].ipaddr,
75: client_info[new_socket].port,
76: new_socket);
77: return new_socket;
78: }
79:
80:
81: /*-----------------------------------------------------
82: 引数でソケットディスクリプタを受け取り、そのソケットから
83: read(2) で文字列を読み込み、文字列をそのままクライアントに
84: 送信する。read(2) の戻り値をそのまま返す。
85: -----------------------------------------------------*/
86: int
87: read_and_reply(int sock){
88: int read_size;
89: char buf[BUF_LEN];
90:
91: read_size = read(sock, buf, sizeof(buf)-1);
92:
93: if ( read_size == 0 || read_size == -1 ){
94: printf("%s (%s) ポート %d ディスクリプタ %d 番からの接続が切れました。\n",
95: client_info[sock].hostname,
96: client_info[sock].ipaddr,
97: client_info[sock].port,
98: sock);
99: close(sock);
100: client_info[sock].last_access = 0;
101: } else {
102: /* 文字列終端を \0 で terminate */
103: buf[read_size] = '\0';
104: printf("%s (%s) ポート %d ディスクリプタ %d 番からのメッセージ: %s",
105: client_info[sock].hostname,
106: client_info[sock].ipaddr,
107: client_info[sock].port,
108: sock,
109: buf);
110: write(sock, buf, strlen(buf));
111: time(&client_info[sock].last_access);
112: }
113: return read_size;
114: }
115:
116:
117: int
118: main(){
119: fd_set target_fds;
120: fd_set org_target_fds;
121: int sock_optval = 1;
122: int port = 5000;
123: /* リスニングソケットを作成 */
124: listening_socket = socket(AF_INET, SOCK_STREAM, 0);
125:
126: /* ソケットオプション設定 */
127: if ( setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR,
128: &sock_optval, sizeof(sock_optval)) == -1 ){
129: perror("setsockopt");
130: exit(1);
131: }
132: /* アドレスファミリ・ポート番号・IPアドレス設定 */
133: sin.sin_family = AF_INET;
134: sin.sin_port = htons(port);
135: sin.sin_addr.s_addr = htonl(INADDR_ANY);
136:
137: if ( bind(listening_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0 ){
138: perror("bind");
139: exit(1);
140: }
141:
142: if ( listen(listening_socket, SOMAXCONN) == -1 ){
143: perror("listen");
144: exit(1);
145: }
146: printf("ポート %d を見張ります。\n", port);
147:
148: /* 監視対象のディスクリプタ一覧をゼロクリア */
149: FD_ZERO(&org_target_fds);
150: /* リスニングソケットを監視対象に追加 */
151: FD_SET(listening_socket, &org_target_fds);
152:
153: while (1){
154: int i;
155: time_t now_time;
156: struct timeval waitval; /* select に待ち時間を指定するための構造体 */
157: waitval.tv_sec = 2; /* 待ち時間に 2.500 秒を指定 */
158: waitval.tv_usec = 500;
159:
160: /* org_target_fds を target_fds にコピー */
161: memcpy(&target_fds, &org_target_fds, sizeof(org_target_fds));
162:
163: select(FD_SETSIZE, &target_fds, NULL, NULL, &waitval);
164:
165: /* ソケットが読み出し可能か順にチェック */
166: for ( i=0 ; i<FD_SETSIZE ; i++ ){
167: if ( FD_ISSET(i, &target_fds) ){
168: printf("ディスクリプタ %d 番が読み込み可能です。\n", i);
169:
170: if ( i == listening_socket ){
171: int new_sock;
172: /* 新しいクライアントがやってきた */
173: new_sock = accept_new_client(i);
174: if ( new_sock != -1 ){
175: /* 監視対象に新たなソケットを追加 */
176: FD_SET(new_sock, &org_target_fds);
177: }
178: } else {
179: int read_size;
180: /* 接続済みソケットからデータが送信されてきた */
181: read_size = read_and_reply(i);
182:
183: if ( read_size == -1 || read_size == 0 ){
184: /* 切断したソケットを監視対象から削除 */
185: FD_CLR(i, &org_target_fds);
186: }
187: }
188: }
189: }
190:
191: time(&now_time); /* 現在時刻を取得 */
192:
193: for ( i=0 ; i<FD_SETSIZE ; i++ ){
194: /* 監視対象でないソケットはスキップ */
195: if ( ! FD_ISSET(i, &org_target_fds) ) continue;
196: /* リスニングソケットはスキップ */
197: if ( i == listening_socket ) continue;
198:
199: if ( now_time-10 > client_info[i].last_access ){
200: printf("%s (%s) ポート %d ディスクリプタ %d 番から10秒以上アクセスがありません。切断します。\n",
201: client_info[i].hostname,
202: client_info[i].ipaddr,
203: client_info[i].port,
204: i);
205: close(i);
206: /* 切断したソケットを監視対象から削除 */
207: FD_CLR(i, &org_target_fds);
208: }
209: }
210: }
211: close(listening_socket);
212: return 0;
213: }
| 前へ << C 言語で echo サーバを作ってみよう (1) | C言語で ftp クライアントを作ってみよう (1) >> 次へ |
ご意見・ご指摘は Twitter: @68user までお願いします。