- PR -

CLOSE_WAITについて

投稿者投稿内容
ほいみん
会議室デビュー日: 2006/05/17
投稿数: 5
投稿日時: 2006-05-17 15:13
はじめまして。5月からソケット通信を勉強中のものです。
現在、クライアントからサーバーの常駐プログラム(HTTPDなど)を再起動させようと
思っています。再起動自体は上手くいくのですが、その後サーバープログラムを
終了させ、再度起動させようとすると「bind: Address already in use」という
メッセージがでてきて起動できません。自分なりに調べた結果、サーバーを
netstatしたときに通信をさせているポートがCLOSE_WAITの状態になっている事に
気づきました。

クライアントプログラム自体は
socket()
connect()
send()
recv()
close()
の順番で動作させているだけで、サーバー、クライアント共にcloseしてないという
事はありません。
ソケット通信でのクライアントマシンからサーバーのプログラムの再起動後、CLOSE_WAITが残らないという事はできないのでしょうか。
ご存知の方がいらっしゃいましたら教えて頂きたいと思います。

以下、クライアントとサーバーの送受信部分のソース(C言語)です。

・クライアント
// 受信待ちの状態
while(flg == 0) {
if(size = recv(sock, recv_buf, BUFFSIZE - 1, 0) < 0) {
perror("recv error");
close(sock);
exit(EXIT_FAILURE);
}
recv_buf[size - 1] = '\0';
printf("1.recv_buf[%s]\n", recv_buf);

// ブレイク判定
if(strstr(recv_buf, "END") != NULL) {
flg = 1;
continue;
}
printf("<FONT SIZE=\"1\">%s</FONT><BR>\n", recv_buf);

strcpy(send_buf, "OK");
if(size = send(sock, send_buf, strlen(send_buf), 0) < 0) {
perror("send error");
exit(EXIT_FAILURE);
}
}

close(sock);


サーバー
// ファイルサイズ分読み込む
while(flg == 0) {
if(fgets(send_buf, fsize + 1, fp) == NULL) {
flg = 1;
strcpy(send_buf, "END");
if(size = send(sock, send_buf, strlen(send_buf) + 1, 0) < 0) {
perror("send error");
close(sock);
exit(EXIT_FAILURE);
}
continue;
}
printf("3.send_buf[%s]\n", send_buf);
if(size = send(sock, send_buf, strlen(send_buf) + 1, 0) < 0) {
perror("send error");
close(sock);
exit(EXIT_FAILURE);
}

if(size = recv(sock, recv_buf, BUFSIZE - 1, 0) < 0) {
perror("recv error");
close(sock);
exit(EXIT_FAILURE);
}

if((strcmp(recv_buf, "OK")) == 0) {
continue;
}
fclose(fp);
break;
}
printf("sock closed\\n");
close(sock);
break;
progman
大ベテラン
会議室デビュー日: 2005/06/08
投稿数: 227
投稿日時: 2006-05-17 16:35
sockopt reuse
などで検索するとハッピーになれるかもしれません。
ほいみん
会議室デビュー日: 2006/05/17
投稿数: 5
投稿日時: 2006-05-17 17:20
progmanさま

返信ありがとうございました。
キーワードで検索してみるとsetsockoptにSO_REUSERADDRを使えばいけるのでは?と
思いサーバー側のプログラムに以下の記述を追加しました。
しかし相変わらずCLOSE_WAITのステータスのままで、またサーバープログラムを終了後
再度起動しようとしても「bind: Address already in use」のメッセージが表示され
接続できませんでした。setsockoptで設定する第一引数はbindで指定するソケットで
合っているでしょうか・・・?

val = 1;
val = setsockopt(sock_waiting, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
ぽんす
ぬし
会議室デビュー日: 2003/05/21
投稿数: 1023
投稿日時: 2006-05-17 20:48
引用:

ほいみんさんの書き込み (2006-05-17 15:13) より:
の順番で動作させているだけで、サーバー、クライアント共にcloseしてないという
事はありません。


いや、CLOSE_WAITで止まってるのはサーバがcloseしていないからです。
closeすればLAST_ACKに移行します。
インデントが無くて見づらいのでソースを読んでいませんが、
おそらくどこかでブロックしていて、「ここでcloseするはず」と期待している
位置に到達していないのでしょう。
ほいみん
会議室デビュー日: 2006/05/17
投稿数: 5
投稿日時: 2006-05-18 09:20
ぽんすさま

返信ありがとうございました。

closeする手前で一度printfでcloseを通るかどうかの確認をしていて、そのprintfが
画面に表示されていたので自分としてはcloseしているものと思っていました。
また、このcloseというのはbindやlistenしているソケットではなく、accept時に
生成したソケットをcloseするという認識でよいでしょうか?最初に書くのを忘れて
しまったのですが、サーバーはこちらからCTRL+Cやエラーで強制終了させない限り
ずっと起動している状態にしています。
今回の現象は一度サーバーをCTRL+Cで終了させ、ソースを修正した後に再度起動
しようと思ったときに発見しました。

ソースをインデントして張り付けたつもりだったのですが、インデントされずに
投稿されてしまいました。申し訳ありませんです・・・
ぽんす
ぬし
会議室デビュー日: 2003/05/21
投稿数: 1023
投稿日時: 2006-05-18 23:15
bind(2)が失敗するのと CLOSE_WAIT とは別の問題です。
まだ情報が不足してるとゆーか、おそらくご自分で気づいていない
何かが行われています。

bind()でエラーとなってる理由はエラーメッセージの通りのはずで、
典型的にはサーバプロセスを二重起動しようとした際に起きるものです。
CLOSE_WAITについては前回書いた通り。補足すると、通信相手(どちらが
サーバとかクライアントとかいうことは関係ない)からFINを受け取った
後で、こちらからFINを投げるまでの間の状態です。

Unixでプロセスを終了した場合にはファイルディスクリプタは
閉じられるので、閉じられていないソケットがあるということは
動いているプロセスがあるはず。

引用:

ほいみんさんの書き込み (2006-05-18 09:20) より:
また、このcloseというのはbindやlistenしているソケットではなく、accept時に
生成したソケットをcloseするという認識でよいでしょうか?


(CLOSE_WAITになっている)TCP接続を終了する、ということであれば
accept(2)が返してきたファイルディスクリプタ番号です。

引用:

ソースをインデントして張り付けたつもりだったのですが、インデントされずに
投稿されてしまいました。申し訳ありませんです・・・


CODEタグを使うとインデントが付いたままとなります。
http://www.atmarkit.co.jp/bbs/phpBB/faq-japanese.php

追記。
CLOSE_WAIT のほうは、「accept()の後、fork()して子プロセスに相手を
させるように書いてるんだけど、親プロセスがそのファイルディスクリプタを
閉じてない」とかかなあ...

[ メッセージ編集済み 編集者: ぽんす 編集日時 2006-05-19 08:33 ]
ほいみん
会議室デビュー日: 2006/05/17
投稿数: 5
投稿日時: 2006-05-19 09:34
ぽんすさま

返信ありがとうございました。
bindの失敗とCLOSE_WAITが別問題とは思っていませんでした。
というのは、クライアント側PCA(192.168.0.1)とサーバー側PCB(192.168.0.2)で
PORT12345で通信させた場合、netstatでサーバー側を見ると以下のように
なっていました(サーバープログラムをCTRL+Cで終了させた場合)。

コード:

Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 1 0 192.168.0.1:12345 192.168.0.2:37689 CLOSE_WAIT



この状態でサーバープログラムを起動させようとすると、12345のPORTが
クライアント側の37689というポートに掴まれている為、2重に起動できず
「bind: Address already in use」というエラーが出るという認識だったのですが
間違っていますでしょうか。

引用:

追記。
CLOSE_WAIT のほうは、「accept()の後、fork()して子プロセスに相手を
させるように書いてるんだけど、親プロセスがそのファイルディスクリプタを
閉じてない」とかかなあ...



実はfork()の使い方をちゃんと理解していないのでfork()は使っていません。
以下にインデントしたサーバーのコードを載せてみましたのでご指摘等あれば
頂けないでしょうか。GetFileSizeというのは自作の関数で、ファイルサイズを返すものです。

コード:

#define BUFSIZE 8192
#define PORT 12345
#define FILE_NAME "/var/tmp/check.dat"
#define REHTTP_PROG "/etc/rc.d/init.d/httpd restart"

int main() {

struct sockaddr_in server;
struct sockaddr_in client;
int i, len, ret, val;
int flg, size;
int sum, fsize;
int sock, sock_waiting;
char send_buf[BUFSIZE];
char recv_buf[BUFSIZE];
FILE *fp;

// サーバーのアドレスを設定する
memset((char *)&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_port = htons(PORT);

// TCPでソケットの作成
if((sock_waiting = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

val = 1;
val = setsockopt(sock_waiting, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
if (val < 0) {
perror("setsockopt");
exit(EXIT_FAILURE);
}

// サーバーへのポート番号割り当て
if(bind(sock_waiting, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}

// コネクション確立要求受付開始
listen(sock_waiting, SOMAXCONN);

// コネクション受付ループ
while(1) {
printf("waiting...\n");
}
len = sizeof(client);
if((sock = accept(sock_waiting, (struct sockaddr *)&client, (size_t*)&len)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}

// 接続完了メッセージの送信
strcpy(send_buf, "Connect Success");
if(size = send(sock, send_buf, strlen(send_buf) + 1, 0) < 0) {
perror("send error");
close(sock);
exit(EXIT_FAILURE);
}

i = 0;
sum = 0;
// サーバー処理メインルーチン
while(1) {
if((size = recv(sock, recv_buf, BUFSIZE - 1, 0)) < 0) {
perror("recv error");
close(sock);
exit(EXIT_FAILURE);
} else if(size == 0) {
break;
}
sum += size;

recv_buf[sum - 1] = '\0';

if(strncmp(recv_buf, "rehttp", 6) == 0) {
ret = http_reboot();
}
if(ret != 0) {
perror("error");
close(sock);
exit(EXIT_FAILURE);
}
memset(&recv_buf, 0, sizeof(recv_buf));
flg = 0;

fsize = GetFileSize(FILE_NAME);
if((fp = fopen(FILE_NAME, "r")) == NULL) {
perror("file open error");
close(sock);
exit(EXIT_FAILURE);
}

// ファイルサイズ分読み込む
while(flg == 0) {
if(fgets(send_buf, fsize + 1, fp) == NULL) {
flg = 1;
strcpy(send_buf, "END");
if(size = send(sock, send_buf, strlen(send_buf) + 1, 0) < 0) {
perror("send error");
close(sock);
exit(EXIT_FAILURE);
}
continue;
}
if(size = send(sock, send_buf, strlen(send_buf) + 1, 0) < 0) {
perror("send error");
close(sock);
exit(EXIT_FAILURE);
}

if(size = recv(sock, recv_buf, BUFSIZE - 1, 0) < 0) {
perror("recv error");
close(sock);
exit(EXIT_FAILURE);
}

if((strcmp(recv_buf, "OK")) == 0) {
continue;
}
fclose(fp);
break;
}
close(sock);
printf("sock closed\n");
break;
}
}
close(sock_waiting);
return EXIT_SUCCESS;
}

int http_reboot()
{
int ret;
char buff[MAX_LENGTH];

sprintf(buff, "%s > %s", REHTTP_PROG, FILE_NAME);
ret = system(buff);

return(ret);

}




[ メッセージ編集済み 編集者: ほいみん 編集日時 2006-05-19 09:35 ]

[ メッセージ編集済み 編集者: ほいみん 編集日時 2006-05-19 09:38 ]
ぽんす
ぬし
会議室デビュー日: 2003/05/21
投稿数: 1023
投稿日時: 2006-05-19 22:44
繰り返しますが、CLOSE_WAITで止まっているということは
サーバプロセスがまだ動いていることを意味します。
すなわち、エラーの原因は「サーバプロセスの二重起動」です。

ソースのほうですが、このままではコンパイルも通らない状態ですので
printf("waiting...\n");
の次の行の } は無いものと考えます。

気になるところは多いですが、CLOSE_WAITに関して目に付いた箇所だけ。
} else if(size == 0) {
の節に入るのはrecv()が0を返したとき、すなわちクライアントから
切断してきた場合です。この節に入ると接続を閉じないまま最外周の
whileループの先頭に行くことになるので、サーバプロセスはaccept()で
待ち続けます。それまでのクライアントとの接続は、クライアント側からは
すでに閉じられているけれどサーバ側では放置しているのでCLOSE_WAITと
なります。

スキルアップ/キャリアアップ(JOB@IT)