LoginSignup
2
6

More than 3 years have passed since last update.

ソケットプログラミング タイムアウト付きconnect

Posted at

概要

クライアントサイドのソケットプログラミングでタイムアウト付きコネクトをする実装です。

昔実装したものを思い出し思い出し書き起こしました。

connect()は同期型ですのでエラーか接続するまで待ってしまいます。
永遠に待たれると困る場合はタイムアウト機能が欲しくなりますが、connect()自体には任意の時間でのタイムアウトするような機能はありません。

タイムアウトを行うためには、一度非同期にしてからconnect()を行います。
その後、同期型に戻してからselect()で待ちます。
このselect()でタイムアウトを実現します。

以下にコードを記載します。

LinuxC_TimeoutConnect.cpp

//タイムアウト付きコネクト(非同期コネクト)
int Connect(SOCKET & socket, count struct sockaddr * name, int namelen, timeval timeout)
{
    //接続前に一度非同期に変更
    int flags = fcntl(soket, F_GETFL);
    if(-1 == flags)
    {
        return -1;
    }
    int result = fcntl(socket, F_SETFL, flags | O_NONBLOCK);
    if(-1 == result)
    {
        return -1;
    }

    //接続
    reuslt = connect(socket, name, namelen);
    if(SOCKET_ERROR == reuslt)
    {
        if(EINPROGRESS == errno)
        {
            //非同期接続成功だとここに入る。select()で完了を待つ。
            errno = 0;
        }
        else
        {
            //接続失敗 同期に戻す。
            fcntl(socket, F_SETFL, flags );
            return -1;
        }
    }

    //同期に戻す。
    result = fcntl(socket, F_SETFL, flags );
    if(-1 == result)
    {
        //error
        return -1;
    }

    //セレクトで待つ
    fd_set readFd, writeFd, errFd;
    FD_ZERO(&readFd);
    FD_ZERO(&writeFd);
    FD_ZERO(&errFd);
    FD_SET(socket, &readFd);
    FD_SET(socket, &writeFd);
    FD_SET(socket, &errFd);
    int sockNum = select(socket + 1, &readFd, &writeFd, &errFd, &timeout))
    if(0 == sockNum)
    {
        //timeout error
        return -1;
    }
    else if(FD_ISSET(socket, &readFd) || FD_ISSET(socket, &writeFd) )
    {
        //読み書きできる状態
    }
    else
    {
        //error
        return -1;
    }

    //ソケットエラー確認
    int optval = 0;
    socklen_t optlen = (socklen_t)sizeof(optval);
    errno = 0;
    result = getsockeopt(socket, SOL_SOCKET, SO_ERROR, (void *)&optval, &optlen);
    if(result < 0)
    {
        //error
    }
    else if(0 != optval)
    {
        //error
    }

    return 0;
}

Linux の場合はソケットオプションを読みだして、SO_ERRORが0かを確認する必要があります。
参考:Man page of CONNECT の エラー、EINPROGRESS

Windowsでは必要ないです。

また、Linux版のselect()では引数に渡したタイムアウト値(ここではtimeval timeout)の値を更新してしまいます。
Windows版では更新しませんでした。

以下、Windows版です。

WinC_TimeoutConnect.cpp

//タイムアウト付きコネクト(非同期コネクト)
int Connect(SOCKET & socket, count struct sockaddr * name, int namelen, timeval timeout)
{
    //接続前にいったん非同期に変更
    unsigned long mode = 1;
    int result= ioctlsocket(socket, FIONBIO, &mode);
    if(NO_ERROR != result)
    {
        return -1;
    }

    //接続
    result= connect(socket, name, namelen);
    if(SOCKET_ERROR == result)
    {
        int errono = WSAGetLastError();
        if(WSAEWOULDBLOCK == errono)
        {
            //非同期接続成功だとここに入る。select()で完了を待つ。
            errno = 0;
        }
        else
        {
            //接続失敗
            //同期型に戻す。
            mode = 0;
            ioctlsocket(socket, FIONBIO, &mode);
            return -1;
        }
    }

    //同期型に戻す。
    mode = 0;
    result= ioctlsocket(socket, FIONBIO, &mode);
    if(0 < result)
    {
        //error
        return -1;
    }

    //セレクトで待つ
    fd_set readFd, writeFd, errFd;
    FD_ZERO(&readFd);
    FD_ZERO(&writeFd);
    FD_ZERO(&errFd);
    FD_SET(socket, &readFd);
    FD_SET(socket, &writeFd);
    FD_SET(socket, &errFd);
    int sockNum = select(socket + 1, &readFd, &writeFd, &errFd, &timeout))
    if(0 == sockNum)
    {
        //timeout
        return -1;
    }
    else if(FD_ISSET(socket, &readFd) || FD_ISSET(socket, &writeFd) )
    {
        //読み書きできる状態
    }
    else
    {
        //error
        return -1;
    }


    return 0;
}

2
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
6