[Win32]プログラム異常終了後Listenゾンビプロセス(?)になる

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
たろ

[Win32]プログラム異常終了後Listenゾンビプロセス(?)になる

#1

投稿記事 by たろ » 13年前

お世話になっております。
趣味で簡易HTTPサーバプログラムを作っていますが、プログラムが異常終了した後
ゾンビのように生き残ってしまう現象を回避・改善したく質問させて頂きます。

環境:WinXP Home SP3、VC2008Express、C言語+Win32API

・プログラムはウィンドウを1つ持ち、
・winsock2で接続を待ち受けるTCPサーバプログラムです。
・bind、listen後、NULLポインタアクセス違反が発生すると、プロセスは強制終了させられますが、
・なぜかlistenしたポートが解放されず、netstat -a で見ると「LISTENING」のままになっています。
・そのためか、ふたたびプログラムを起動しても、bindエラーが発生してしまいます。
・しばらく放置しても回復しません。
・タスクマネージャのプロセス一覧には、問題のプロセスは存在しません。
・netstat -ao で問題のプロセスIDは表示確認できます。
・そのプロセスIDを、OpenProcess/TerminateProcess すると、エラー ERROR_ACCESS_DENIED(5) になります。

このような、ゾンビみたいなプロセスをきれいに葬り、ポートを解放する方法はないでしょうか?
できればOS再起動せずに回復させたいです。

以下は、私の環境で問題が発生する小プログラムです。

コード:

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "ws2_32.lib")

#include <winsock2.h>

LRESULT CALLBACK WindowProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
{
    switch( msg ){
    case WM_CREATE:
        {
            SOCKET sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
            SOCKADDR_IN addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons( 54326 );
            addr.sin_addr.s_addr = INADDR_ANY;
            if( bind( sock, (SOCKADDR*)&addr, sizeof(SOCKADDR) )==SOCKET_ERROR ){
                MessageBox(hwnd,"bindエラー終了します","エラー",MB_ICONERROR);
                return -1;
            }
            listen( sock, 5 );
            SetWindowText( hwnd, "listenしました" );
        }
        CreateWindow(
            "button", "ぬるぽゾンビ化", WS_CHILD |WS_VISIBLE
            ,20,20,200,30, hwnd, (HMENU)1, GetModuleHandle(NULL),NULL
        );
        break;

    case WM_COMMAND:
        if( LOWORD(wp)==1 ) strlen( NULL );
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc( hwnd, msg, wp, lp );
}

int WINAPI WinMain( HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow )
{
    WSADATA wsaData;
    WNDCLASS wc;
    MSG msg;

    WSAStartup( MAKEWORD(2,2), &wsaData );

    memset( &wc, 0, sizeof(wc) );
    wc.lpfnWndProc   = WindowProc;
    wc.hInstance     = hinst;
    wc.hCursor       = LoadCursor(NULL,IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
    wc.lpszClassName = "MainForm";
    RegisterClass(&wc);

    CreateWindow(
            "MainForm", "ゾンビ"
            ,WS_OVERLAPPEDWINDOW |WS_VISIBLE
            ,CW_USEDEFAULT, CW_USEDEFAULT, 300, 100
            ,NULL,NULL, hinst,NULL
    );

    while( GetMessage( &msg, NULL, 0, 0 ) >0 ){
        TranslateMessage( &msg );
        DispatchMessage( &msg );
    }

    WSACleanup();
    return (int)msg.wParam;
}

アバター
へにっくす
記事: 634
登録日時: 13年前
住所: 東京都

Re: [Win32]プログラム異常終了後Listenゾンビプロセス(?)になる

#2

投稿記事 by へにっくす » 13年前

・・・closesocketないやん。
いや、closesocketあってもstrlen(NULL)でわざと強制終了させるようじゃ意味ないな
だって通常の終わり方しないから(strlen(NULL)を実行した後、プロセスが終わるので、その後の処理が行われない)。
そもそも何でそういうことをしてるんでしょうか?
プログラム異常終了させてんだからシステムが不安定になるのは当たり前。
written by へにっくす

たろ

Re: [Win32]プログラム異常終了後Listenゾンビプロセス(?)になる

#3

投稿記事 by たろ » 13年前

>へにっくすさん
このプログラムは、「Listenプロセスが異常終了するとゾンビみたいになる」という現象を
起こすだけのテスト用で、今回の質問のために作りました。
自作HTTPサーバ開発中に発生した現象でした。

NULLポインタに気をつけるのはもちろんですが、コード量が増えると完全に防ぐのは難しく、
開発中はなおさらしばしば出会う現象とおもいます。

問題は異常終了後「使っていたポートで再Listenできない、つまりサーバ利用不能」な点です。

IISをはじめとするWindows用サーバアプリは、この現象をどうやって回避しているのか・・?
まさか「アプリ異常終了したらOS再起動するまで回復しません」ではないだろう、そんな手間
のかからない回避方法があるのでは?と考えて質問しました。

その後ネットであれこれ検索して、setsockopt の SO_REUSEADDR オプションを知りました。
bind 前にこれを実行すると、bind も listen も成功して、特に問題なく動作するようです。

コード:

int on = 1;
setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on) );
SO_REUSEADDR は、TIME_WAIT 状態でも bind するためのオプションとのことで、
今回のListenゾンビとはちょっと違うような気もしますが・・まあ動いてるからいいか・・。

ただ、動くようにはなりましたが、netstat -a を見ると、相変わらず異常終了したポートは
ずっと「LISTENING」のままです。telnet でもポートに接続はできます。応答はありません。

このListenゾンビを葬り去ることはできないんでしょうか・・。
メモリもファイルハンドルも、アプリ異常終了しても特に問題にはならないのに、
ソケットはわりと簡単にダメになる(Windowsがリソース解放しきれない)んでしょうか・・。

アバター
shiro4ao
記事: 224
登録日時: 15年前
住所: 広島

Re: [Win32]プログラム異常終了後Listenゾンビプロセス(?)になる

#4

投稿記事 by shiro4ao » 13年前

メモリもファイルハンドルも個々のプロセスのもつリソースで、おなじハンドル値でもプロセスが異なれば違うものを指してます。
(ハンドルも実はハンドルを管理しているテーブルのインデックスでありプロセス間に関連は全くない。
  あるプロセスでウィンドウ用に使ってるハンドルが"42"でも隣のプロセスのハンドル"42"は全然違うものを指してる)
基本的にはプロセスが死ぬと同時に"誰も使ってない"と判断されたメモリやハンドルは開放されます。

一方、ポート番号はいかなるプロセスから見ても同じものを指すので
一人がこまったことをするとみんなに迷惑がかかります。
(誰が使おうがポート1245番は誰にとっても1245番)

なんというか、サーバは「不意に落ちない、定期的な再起動を求めない、ぶっ続けで動き続ける」という状態が求められるので
落ちないようにするのが一番だと思います。
(「できる限り被害を抑えて故障する」という考えもあるといえばあるのでしょうが・・・・・・)

その上で、「リソースをできる限り正しく開放して落ちる」ことができるかはわかりません。
IISとかApacheとかどういう実装になっているんでしょうね。

私もWinsockアプリケーションの作成中には落ちてポートがbindエラー(たぶん落ちたプロセスが開放してない)に
なることがありました。
そんな時はとりあえず、使うポートを変えて、あんまり溜まってきたら、再起動をかけていました。

ログオフなどで戻るかどうかはわかりません。いつか試してみたいですが。



/*追記*/
上記はWindowsの話です。
Linuxでの挙動はよくわかりません。
Linux(Ubuntu)でbindしたゾンビをいっぱい増やしてみたんですが、
しばらくするとみんな消えてしまっていました。
Windowsと現象が違うのかもしれません。

たろ

Re: [Win32]プログラム異常終了後Listenゾンビプロセス(?)になる

#5

投稿記事 by たろ » 13年前

>shiro4aoさん
Linuxでの実験ありがとうございます。
私も試しにVMのCentOS5.5でテスト用ListenプロセスをSegmentation faultで落としてみたところ、
netstatでは「TIME_WAIT」になって、しばらくすると消えていました。TIME_WAITで残っている時は
setsockopt(SO_REUSEADDR)が有効に働くようです。

WindowsよりLinuxの方がこのあたりのリソース回収はうまく動くのかな・・と思いつつ、Windows
に戻って、一度ログオフしたところ、Listenゾンビが消えていました!

・・と共に、問題が再現しなくなりました・・orz

ログオフ前は、Listenゾンビが確実に発生していたのですが、今は同じことをしても問題ありません。
netstat でもすぐに消えて、setsockopt(SO_REUSEADDR) を実行しなくても bind は成功します。

問題が消えてしまいました・・いったい何だったんでしょう・・?
たまたまOSのリソース回収機能がうまく動いていなかったのか・・?

お騒がせな内容になってしまい申し訳ないです。。
追求が難しそうですし解決とさせて頂きます。。

閉鎖

“C言語何でも質問掲示板” へ戻る