winsock使用時にアプリケーション異常が発生した場合の対処法

[戻る]
TERU 2006/03/09(木) 17:42:14
はじめましてTERUと申します。

今回VC++でTCP通信を行う作業を始めましたが初心者なので行き詰ってしまいました。
基本的な事かもしれませんがどなたかお分かりになる方がいらっしゃいましたら
御教授の程、お願い致します。

私の認識を確認したいのですがTCP通信(クライアント側)を行う際、手続きとして
 1.WinSockの初期化(WSAStartup使用)
 2.ソケットを作成(socket使用)
 3.サーバのIPアドレス取得(inet_addr、gethostbyname使用)
 4.サーバに接続(connect使用)
 5.データの送受信(send、recv使用)
 6.送受信の無効化(shutdown使用)
 7.ソケットの破棄(closesocket使用)
 8.WinSockのリソース解放(WSACleanup使用)
おおまかですが上記の様になると思っております。
上記を基に1回のみ送受信を行う確認用アプリケーションを作成しました。

ここからが質問になりますが
6.以降の処理を行う前にアプリケーションがハングアップして(アプリケーションに問題が
あると思いますが・・・)正常な手続きが行えなかった場合、1度送受信をして再度アプリケ
ーションを起動しconnectを行おうとするとconnectできません。
現在、6.以降が実行されない事を想定し6.以降をコメントにし実行しています。

デバッグで確認した所connectでエラーとなっている事は分かりましたが
再度connect出来る様にするにはどの様な対処法が必要なのかが分かりません。

申し訳ございませんがどなたか御教授の程、宜しくお願い致します。

KING・王 2006/03/09(木) 17:54:36
> デバッグで確認した所connectでエラーとなっている事は分かりましたが
> 再度connect出来る様にするにはどの様な対処法が必要なのかが分かりません。

とりあえず、connectの戻り値や、WSAGetLastErrorを用いて、connectが失敗している原因を
特定しましょう。

#対処法を考えるには、原因を突き止めないと、対象ができません。

TERU 2006/03/09(木) 18:03:16
KING・王様、早速の御回答ありがとうございます。

一応、下記の様な記述をしデバッグ確認しておりますがswitchまでは実行される
もののswitchの分岐は行われていません。

引き続き御教授の程、宜しくお願い致します。

/* 指定のソケットでサーバへコネクトします */
if(connect(soc,(struct sockaddr *)&serversockaddr,sizeof(serversockaddr)) == 
SOCKET_ERROR){
switch(WSAGetLastError()) {
    case WSABASEERR:
        break;
    case WSAEINTR:
        break;
    case WSAEBADF:
        break;
    case WSAEACCES:
        break;
    case WSAEFAULT:
        break;
    case WSAEINVAL:
        break;
    case WSAEMFILE:
        break;
    case WSAEWOULDBLOCK:
        break;
    case WSAEINPROGRESS:
        break;
    case WSAEALREADY:
        break;
    case WSAENOTSOCK:
        break;
    case WSAEDESTADDRREQ:
        break;
    case WSAEMSGSIZE:
        break;
    case WSAEPROTOTYPE:
        break;
    case WSAENOPROTOOPT:
        break;
    case WSAEPROTONOSUPPORT:
        break;
    case WSAESOCKTNOSUPPORT:
        break;
    case WSAEOPNOTSUPP:
        break;
    case WSAEPFNOSUPPORT:
        break;
    case WSAEAFNOSUPPORT:
        break;
    case WSAEADDRINUSE:
        break;
    case WSAEADDRNOTAVAIL:
        break;
    case WSAENETDOWN:
        break;
    case WSAENETUNREACH:
        break;
    case WSAENETRESET:
        break;
    case WSAECONNABORTED:
        break;
    case WSAECONNRESET:
        break;
    case WSAENOBUFS:
        break;
    case WSAEISCONN:
        break;
    case WSAENOTCONN:
        break;
    case WSAESHUTDOWN:
        break;
    case WSAETOOMANYREFS:
        break;
    case WSAETIMEDOUT:
        break;
    case WSAECONNREFUSED:
        break;
    case WSAELOOP:
        break;
    case WSAENAMETOOLONG:
        break;
    case WSAEHOSTDOWN:
        break;
    case WSAEHOSTUNREACH:
        break;
    case WSAENOTEMPTY:
        break;
    case WSAEPROCLIM:
        break;
    case WSAEUSERS:
        break;
    case WSAEDQUOT:
        break;
    case WSAESTALE:
        break;
    case WSAEREMOTE:
        break;
    case WSASYSNOTREADY:
        break;
    case WSAVERNOTSUPPORTED:
        break;
    case WSANOTINITIALISED:
        break;
    case WSAEDISCON:
        break;
    case WSAENOMORE:
        break;
    case WSAECANCELLED:
        break;
    case WSAEINVALIDPROCTABLE:
        break;
    case WSAEINVALIDPROVIDER:
        break;
    case WSAEPROVIDERFAILEDINIT:
        break;
    case WSASYSCALLFAILURE:
        break;
    case WSASERVICE_NOT_FOUND:
        break;
    case WSATYPE_NOT_FOUND:
        break;
    case WSA_E_NO_MORE:
        break;
    case WSA_E_CANCELLED:
        break;
    case WSAEREFUSED:
        break;
    case WSAHOST_NOT_FOUND:
        break;
    case WSATRY_AGAIN:
        break;
    case WSANO_RECOVERY:
        break;
    case WSANO_DATA:
        break;
}
}

kure 2006/03/09(木) 18:24:08
> switchの分岐は行われていません。

そらそうだ・・・。
switchでどこに分岐したか知りたいならcaseの中に何か書きましょう。

それと個のコードだとcase XXX:で書いたものしか判らないので
WSAGetLastErrorで取得した値を直接表示した方が良いと思いますよ。
ConsoleアプリでないのであればOutputDebugStringでデバッガの出力に出せますし。

> connectを行おうとするとconnectできません。

推測ですがポートがTIME_WAITになってるんじゃないでしょうか?
setsockoptでSO_REUSEADDRを指定してみたら解決するかも?

TERU 2006/03/09(木) 19:25:27
kure様 御教授ありがとうございます。

> switchでどこに分岐したか知りたいならcaseの中に何か書きましょう。
その通りでした。break;の前行にprint文を記述した結果分岐されました。

分岐された場所は『WSAECONNREFUSED』(接続が拒否された。)でした。
なぜ接続が拒否されたのかが不明ですが・・・。

netstatで確認した結果使用しているポートは『LISTENING』でした。
TIME_WAITになっていれば『TIME_WAIT』と出るのでしょうか?

御教授お願い致します。

kure 2006/03/09(木) 20:35:33
> netstatで確認した結果使用しているポートは『LISTENING』でした。

これってクライアント側ですか?
それともサーバー側ですか?

> TIME_WAITになっていれば『TIME_WAIT』と出るのでしょうか?

TIME_WAITならTIME_WAITって出るはずだし、
接続済みであればESTABLISHで、
接続待機であればLISTENINGになりますよ。

この辺が参考になるかな?

http://www.atmarkit.co.jp/fwin2k/win2ktips/234netstat/netstat.html

> 6.以降をコメントにし実行しています。

見落としてた・・・。
shutdownはきちんと呼び出さないと。
相手側に通信終わりだよっていうことを伝えるのがshutdownなので
これを呼び出さないと相手はまだ通信中と捉えて送信や受信処理を継続しようとします。

TERU 2006/03/10(金) 08:53:44
kure様 ありがとうございます。

> これってクライアント側ですか?
> それともサーバー側ですか?
クライアント側です。

何度確認してもLISTENINGでした。

> shutdownはきちんと呼び出さないと。
重々承知しておりますが万が一shutdownが行われずにアプリケーションがハングアップ
した時の事を想定しております。
(ハングアップしないプログラムを作るのが当然ですが・・・)

shutdownをせずにアプリケーションが終わってしまった場合はどの様な対処を
行えば回復できるのでしょうか?

ちなみにサーバ側を再立ち上げするとconnectできます。

宜しくお願い致します。

kure 2006/03/10(金) 10:32:04
> クライアント側です。
> 何度確認してもLISTENINGでした。

Listenするのはサーバー側なので、
クライアント側は何かがポート使ってればLISTENINGとかESTABLISH
とかなんかの状態にはなってるでしょう。

サーバー側のポートの状態をみないといけません。

> shutdownをせずにアプリケーションが終わってしまった場合はどの様な対処を
> 行えば回復できるのでしょうか?

ポートが空くまでひたすら待つ(サーバーがタイムアウトを検出するまで)
かサーバを再起動するしかありません。

詳しくはこのあたりをどうぞ。SocketのFAQになってます。
ここの2.7とか2.8あたりが参考になるかと。

http://www.kt.rim.or.jp/~ksk/sock-faq/unix-socket-faq-ja-2.html

TERU 2006/03/10(金) 10:59:03
> サーバー側のポートの状態をみないといけません。
サーバ側でnetstatを行うと

1.サーバを起動しただけですと
  @
  Loacal  Address : サーバのコンピュータ名:サーバの接続ポート番号
  Foreign Address : サーバのコンピュータ名:0
    Stat            : LISTENING
  のみ表示されています。

2.クライアントが接続(connect)しますと
  @
  Loacal  Address : サーバのコンピュータ名:サーバの接続ポート番号
  Foreign Address : サーバのコンピュータ名:0
    Stat            : LISTENING
  と
  A
  Loacal  Address : サーバのコンピュータ名:サーバの接続ポート番号
  Foreign Address : クライアントのコンピュータ名:クライアントの接続ポート番号?
    Stat            : ESTABLISHED
  が表示されています。

この状態でクライアント側のLANケーブルを抜くと
Aが
  Loacal  Address : サーバのコンピュータ名:サーバの接続ポート番号
  Foreign Address : クライアントのIPアドレス:クライアントの接続ポート番号?
    Stat            : ESTABLISHED
となり再度LANケーブルを接続すると
  Loacal  Address : サーバのコンピュータ名:サーバの接続ポート番号
  Foreign Address : クライアントのコンピュータ名:クライアントの接続ポート番号?
    Stat            : ESTABLISHED
となります。

正常に終了処理を行いますと通常はAが消えますが一旦LANケーブルを抜くと
Aが残っています。

この様な状態がWSAECONNREFUSEDを発生させてしまっている原因でしょうか?

サーバアプリケーションを再起動しますと再度通信を行う事が出来ると言うことは
サーバアプリケーションに問題がありそうでしょうか?

宜しくお願い致します。

memol 2006/03/10(金) 11:09:52
サーバー側がクライアントが死んだことを認識できていないのが
再接続できない理由だと思います。
・クライアントアプリが強制終了した
・回線を抜いた状態でクライアントだけ終了した
などの場合に、サーバー側はクライアントが死んだことを
認識できていない状態になります。
この状態で再度接続しようとしても拒否(WSAECONNREFUSED)されます。
これを解決するにはサーバー側にクライアントの生存確認をする機能が必要です。
上のURL先に書いている通り、
・Keepaliveを使う
・空データなどをサーバー側から送信する
などの方法があります。

TERU 2006/03/10(金) 11:57:41
[[解決]]
memol様 ありがとうございます。

> サーバー側がクライアントが死んだことを認識できていないのが
> 再接続できない理由だと思います。
やはりサーバ側ですか。なんとなくその様な気がしてきてはいたんですが
確信が持てませんでした。

> これを解決するにはサーバー側にクライアントの生存確認をする機能が必要です。
サーバ側の担当者に確認してみます。

取敢えず解決とさせて頂きます。

KING・王 様
kure 様
memol 様

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

古いスレッドには追加発言できません。