クライアント側のSO_REUSEADDRについて

投稿者: Anonymous

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

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

#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 : %Xn", ret);
        return ret;
    }

    // ここでエラーが発生。エラーコードはFFFFFFFF
    ret = connect(sock, (struct sockaddr *)&server, sizeof(server));
    if (ret) {
        closesocket(sock);
        printf("connect error : %Xn", 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;
}

解決

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

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

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

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

SO_LINGERは試しましたか?

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

// ここでエラーが発生。エラーコードはFFFFFFFF

なお、connect()ドキュメントには戻り値は0SOCKET_ERRORでありエラーコードはWSAGetLastError()で取得するように記載されています。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *