32587


簡単なTCPクライアントを自ポート固定で作成しようと思っています。ですが、クライアントからコネクションをクローズすると、次に接続する際に数分待たなければ通信しません。TIME_WAITが関係していることまではわかったので、以下のサイトを参考にSO_REUSEADDRオプションを設定することにしました。ですが、それでも問題は解決されませんでした。一体何が悪いのでしょうか。

以下にサンプルコードを示します。一回目のループではコネクションが成功し、アクティブクローズした後の二回目はconnect()で失敗します。

[code]

    #include <stdio.h>
    #include <winsock2.h>
    
    int communicate(char *deststr, int dstport, int srcport) {
        struct sockaddr_in client;
        struct sockaddr_in server;
        SOCKET sock;
        int ret;
        BOOL yes = 1;
        char inbuf[2048];
    
        sock = socket(AF_INET, SOCK_STREAM, 0);
    
        // SO_REUSEADDR を ON にする
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
    
        // サーバー側のアドレスとポートを指定する
        server.sin_family = AF_INET;
        server.sin_port = htons(dstport);
        server.sin_addr.S_un.S_addr = inet_addr(deststr);
    
        // クライアント側のポートを固定にする
        client.sin_family = AF_INET;
        client.sin_port = htons(srcport);
        client.sin_addr.S_un.S_addr = INADDR_ANY;
    
        // ソケットをバインドする。ここではエラーは起こらない。
        ret = bind(sock, (struct sockaddr *)&client, sizeof(client));
        if (ret) {
            closesocket(sock);
            printf("bind error : %X\n", ret);
            return ret;
        }
    
        // ここでエラーが発生。エラーコードはFFFFFFFF
        ret = connect(sock, (struct sockaddr *)&server, sizeof(server));
        if (ret) {
            closesocket(sock);
            printf("connect error : %X\n", ret);
            return ret;
        } else {
            closesocket(sock);
            printf("returned communicate()\n");
            return ret;
        }
    }
    
    // 指定のアドレス、ポートに接続し、接続できたらすぐさま切断し、次のコネクションを張る
    int main(int argc, char *argv[]) {
        WSADATA wsaData;
    
        WSAStartup(MAKEWORD(2,0), &wsaData);
    
        while(1) {
            int ret = communicate("192.168.0.10", 12345, 23456);
            if(ret) {
                break;
            }
        };
    
        WSACleanup();
    
        return 0;
    }
    
[/code]


CC BY-SA 3.0
2017-02-11T04:09:49.393
32578
2017-03-05T02:02:40.213
2017-02-12T01:04:38.623
18863
18863
question
windows c tcp
クライアント側のSO_REUSEADDRについて


TCP/IPではlocal address / local port / remote address / remote
portの４つのパラメーターでコネクションを識別します。`SO_REUSEADDR`とは、local address / local
portが重複していてもremote address / remote
portが異なれば識別できることを前提に`bind()`での重複チェックをバイパスするオプションです。

ですので今回のように意図的に４パラメーターを一致させると`bind()`は成功し`connect()`で失敗します。このようなことがあるため一般的にはクライアント側はランダムなポート番号を使用します（具体的には`bind()`しません）。

>
> `setsockopt()`の部分をコメントアウトして再度実行した場合でも同様に`connect()`の部分でエラ‌​ーとなりますが、なぜ`bind()`でエラー‌​にならないんでしょうか。

推測ですが、`bind()`の段階ではlocal
addressは`INADDR_ANY`ですが`connect()`の際、適切なアドレスに選択・変更されるので、次の`bind()`の段階では使用中と判断されずに済んでいるのかもしれません。

> `SO_LINGER`は試しましたか？

どうなんでしょう？
クライアント側では成功するかもしれませんが、サーバー側でも４パラメーターが一致していることには変わらないので、接続に失敗する可能性が残るとは思います。

>
[code]

>     // ここでエラーが発生。エラーコードはFFFFFFFF
>  
[/code]

なお、[`connect()`ドキュメント](https://msdn.microsoft.com/en-
us/library/ms737625\(v=vs.85\).aspx)には戻り値は`0`か`SOCKET_ERROR`でありエラーコードは`WSAGetLastError()`で取得するように記載されています。


CC BY-SA 3.0
2017-02-11T08:14:52.790
32587
2017-02-11T22:36:05.710
2017-02-11T22:36:05.710
4236
4236
32578
answer


以下、回答ではありませんが、気になった点を2つ挙げます。

  * client, server は一旦 0 でクリアした方が良いです。
[code]     memset(&client, 0, sizeof client);

    memset(&server, 0, sizeof server);
    
[/code]

かなり昔のことですが、クリアしなかった場合に繋がらなかった経験があります。

  * `setsockopt()` にも返り値がありますので、確認した方がよろしいかと思います。

以上です。


CC BY-SA 3.0
2017-02-11T14:14:59.003
32591
2017-02-11T14:14:59.003
5288
32578
answer


こんにちは。

TCPプロトコルに関わる仕事をしています。

SO_LINGERのタイムアウト０秒でl_onoff = 1とl_linger = 0を指定した状態で  
closesocket（Winsockの場合）した場合、TCPプロトコルスタック側で未送信のデータがあった場合はRSTを返します。  
RSTを返された対向側（サーバ側）のソケットは即座にCLOSEDに状態遷移します。

逆にRSTを返さずアクティブクローズでFIN+ACK,ACK,FIN+ACK,FINの通常シーケンス手順を踏むと  
CLOSEDに状態遷移するまで過程を踏むことになるので待受ソケットが完全に空きになるまで  
若干タイムラグが発生すると考えます。（パッシブクローズ側はLAST-ACKでACKを待ってCLOSEDに遷移するので）

Wiresharkなどでパケットキャプチャを見ていないので憶測になるのですが、  
切断して即座に２回目の接続をする時、ホスト（接続元）のSYNに対して対向（サーバ側）は  
RSTを返しているのではないでしょうか？  
SYNに対してRSTを返しているという事は接続しようとしているポートで待受している  
ソケットがサーバ側に存在しないことを意味します。

これに対処する為、サーバ側のバックログ設定が「1」などで設定されていないかを確認してください。  
具体的にはlisten(sock, 1);とかになっているものをlisten(sock, 10);などにしてバックログを増やします。  
この設定を行うことで接続待ちソケットの数を増やす事が出来ます。  
もし、通常のBSDソケットのAPIを使っていない環境であれば同一ポートの  
TCPソケットを複数用意してacceptに準じた処理をソケットの数分実行してみてください。

この対応を行うことでおそらく即座に再接続してもセッション確立出来るのではないでしょうか。  
クライアント側の処理は最初に投稿していただいたコードで問題なさそうなので、  
原因はサーバ側の設定にある気がしています。


CC BY-SA 3.0
2017-03-05T02:02:40.213
33101
2017-03-05T02:02:40.213
20931
32578
answer