概要
クライアントサイドのソケットプログラミングでタイムアウト付きコネクトをする実装です。
昔実装したものを思い出し思い出し書き起こしました。
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;
}