ここでは、Linuxでオープン可能なファイルの数を調べて実際にソケットがいくつ
作れるかを試すプログラムを作ります。
Linuxをチューニングしてたくさんの沢山のクライアントに対応するサーバを作るには
どうすれば良いかの一つの答えとしてselectをpollに置き換える方法を説明します。
Linuxのチューニング:オープン可能なファイルの数を増やす
プログラムの変更 :selectをpollに変更する

オープン可能なファイルの数を調べてみましょう。
ulimit コマンドを使用します。
ulimitコマンドはシステムリソースの制限を設定するためのコマンドです。
プロセスがメモリやCPUタイム等を多く消費しないようにするために、
ユーザごとに制限を設定することができます。
また、オプション指定により、ハードリミット、ソフトリミットを指定する事が
できます
ulimit -H -aコマンドを実行し、現在のハードリミット設定を確認します。
ulimit -S -aコマンドを実行し、現在のソフトリミット設定を確認します。
RaspberryPiでは次のような結果が表示されます。

ulimit -H -a
ulimit -S -a

それでは、実際にソケットがいくつ作れるか調べてみましょう。
SockTestというプログラムを作成します
起動引数で指定された数だけ、socket関数が成功する限りソケットを作ります。

プロジェクトの作成
ファイル→新規作成→クロスプラットフォーム→メイクファイルプロジェクト
プロジェクト名:SockTest
「ソリューションのディレクトリを作成する」のチェックを外します。
stdThread.cpp, stdThread.hをリンクから取得し、既存の項目として追加します。
makefileをSimpleClientなどからコピーし、既存の項目として追加します。
makefileを変更します。
SockTest.cppを新規に作成します。
プロパティの設定は、ここを参考に行ってください。

【makefile】 CC=g++ -g3 -O0 #CC=g++ PROGRAM=SockTest OBJS=SockTest.o stdThread.o SRCS=$(OBJS:%.o=%.cpp) INCLUDE=stdThread.h LFLAGS=-lpthread $(PROGRAM):$(OBJS) $(SRCS) $(INCLUDE) $(CC) -o $(PROGRAM) $(SRCS) $(LFLAGS)

SockTest.cppです。
//(1), //(2)でコメントしてある箇所は後程、解説します。
このプログラムは、単に引数で指定された数分、ソケットを作成します。
作成に失敗したらエラーを表示します。エラー表示関数は、stdThread.cppにあります。
作成に成功したら、何番目、ソケットディスクリプタ、ソケットディスクリプタの最大値を
表示します。

【SockTest.cpp】 #include “stdThread.h” #ifdef _MSC_VER // Windowsのとき #define DISABLE_C4996 __pragma(warning(push)) __pragma(warning(disable:4996)) #define ENABLE_C4996 __pragma(warning(pop)) #else // Linuxのとき #define DISABLE_C4996 #define ENABLE_C4996 #endif BOOL DestroySocket(SOCKET &fd); int main(int argc, char *argv[]) { int iRet = -1, iMaxSockNum, ii; SOCKET *pSockets = NULL, fdMax = 0; //(1) fd_set rfds; //(2) pollfd *fds = NULL; // 起動パラメータチェック if (argc != 2) { fprintf(stderr, “Usage: %s <SockeNum>\n”, argv[0]); goto L_END; } if ((iMaxSockNum = atol(argv[1])) <= 0) { fprintf(stderr, “Usage: SockNum must be at least 1.\n”); goto L_END; } // ソケットの破棄のためにソケットを記録しておくエリアの確保と初期化 pSockets = (SOCKET *)calloc(iMaxSockNum, sizeof(SOCKET)); memset(pSockets, (int)INVALID_SOCKET, iMaxSockNum * sizeof(SOCKET)); //(1) FD_ZERO(&rfds); //(2) fds = (pollfd *)calloc(iMaxSockNum, sizeof(pollfd)); for (ii = 0; ii < iMaxSockNum; ++ii) { // ソケットの作成 if ((pSockets[ii] = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { DispErrorMsg(“Err:socket”); break; } fdMax = max(fdMax, pSockets[ii]); // 一番大きいソケットを調べる // 作成できたソケットを表示 fprintf(stderr, “%4d %4d %4d\n”, ii + 1, (int)pSockets[ii], (int)fdMax); //(1) FD_SET(pSockets[ii], &rfds); //(2) fds[ii].fd = pSockets[ii]; } // ソケットの破棄 for (ii = 0; ii < iMaxSockNum; ++ii) { if (pSockets[ii] != INVALID_SOCKET) { DestroySocket(pSockets[ii]); } } iRet = 0; SAFE_FREE(pSockets) //(2) SAFE_FREE(fds) L_END: return(iRet); } //////////////////////////////////////////////// // function // TCPソケットの破棄 // parameter // SOCKET &fd[in/out]破棄するソケット // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DestroySocket(SOCKET &fd) { // fprintf(stderr, “DestroySocket()\n”); if (fd != INVALID_SOCKET) { shutdown(fd, SHUT_RDWR); // 受信も送信も停止 close(fd); fd = INVALID_SOCKET; } return(TRUE); }

./SockTest 1500
で動かしてみると1021個ソケットを作成後、エラーがでます。
Err:socket 24 Too many open files
標準入力0、標準出力1、標準エラー2の3ファイルディスクリプタを加えて
1024個のファイルディスクリプタをオープン後、エラーということで
ulimit -S -aで分かった、ソフトリミットまでしかオープンできないことが
わかりました。

オープン可能なファイルの数を増やすには、3つの方法があります。
1.カーネルヘッダファイル中の INR_OPEN の値を書き変えて rebuild する。
これは、考えただけでも大変そうですね。
2.ulimit -n を使って、一時的に制限を変更する。
ここでは、これをやってみます。
3./etc/security/limits.conf の設定を書き変え、制限を変更する。
これもできますね。
ulimit -S -n 2000
でソフトリミットを2000にして
./SockTest 1500
を実行します。
うまく1500個のソケットが作れました。

それでは、この状態でSimpleServerがたくさんの接続に対応したサーバとして動くことが
できるか調べてみましょう。
//(1)のコメントアウトを外してください。
これらはselectを使って受信データや送信可能の監視を行うために必要なコードです。
ビルドして
./SockTest 1500
を実行してみてください。
変な挙動をして、異常終了します。

これはfd_setの構造の問題ですので、このまま(select)では、沢山のソケットに
対応できません。

//(1)のコメントを元(コメントアウト)に戻して、//(2)のコメントアウトを外して
ビルドして、実行すると問題なく動作します。
この//(2)の部分がpollを使用するために必要なコードです。

Linux 2.1.23 以降では、Windowsのfd_setに似たような構造体pollfdを使って
selectの代わりにpollをつかって、大きな値のファイルディスクリプタ(ソケット)を
扱うことができます。

struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ };

監視したいソケット分のpollfd配列を作ります。
それぞれの要素のfdにソケットeventsに監視イベントをセットします。
POLLIN   読み出し可能なデータがある。
POLLPRI   読み出し可能な緊急データ (urgent data) がある
POLLOUT  書き込みが可能になった。
POLLRDHUP ストリームソケットの他端が、コネクションを close したか、
       コネクションの書き込み側を shutdown した。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
を呼び出し、監視を行う。
fds pollfd配列のポインタ
nfds 配列の数
timeout タイムアウト時間(ミリ秒)

pollから戻ってきたら、pollfd配列の各要素のreventsを調べ発生したイベントに応じた
処理を行う。
POLLIN 読み出し可能なデータがある。
POLLPRI 読み出し可能な緊急データ (urgent data) がある
POLLOUT 書き込みが可能になった。
POLLRDHUP ストリームソケットの他端が、コネクションを close したか、
コネクションの書き込み側を shutdown した。
POLLERR エラー状態
POLLHUP ハングアップした
POLLNVAL 不正な要求: fd がオープンされていない

それでは、Linux版SimpleServerを変更します。
更新済みstdThread.cpp, stdThead.hに置き換えてください。
poll.h, DispErrorMsgが追加されています。
以下に変更する関数を記述します。(selectに関係している関数)

//////////////////////////////////////////////// // function // 接続要求の受け入れ // parameter // なし // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DoAccept() { BOOL fRet = FALSE; sockaddr_storage ClntAddr; // 接続クライアントのアドレス情報 char szHostAddr[NI_MAXHOST]; socklen_t iClntLen; // 接続クライアントのアドレス情報のサイズ int ii; pollfd fds[MAX_SOCKET_NUM] = {0}; //★変更箇所 fprintf(stderr, “DoAccept()\n”); for (ii = 0; ii < m_iSockCount; ++ii) //★変更箇所 { fds[ii].fd = m_fdServer[ii]; fds[ii].events = POLLIN; // 受信イベントを設定 } while (1) { poll(fds, m_iSockCount, 10); //★変更箇所 // キー入力で中断 if (CheckKey()) { fprintf(stderr, ” OK:Abort by key\n”); break; } for (ii = 0; ii < m_iSockCount; ++ii) { if (fds[ii].revents & POLLERR) //★変更箇所 エラー発生 DispErrorMsg(“Err:DoAccept”); else if(fds[ii].revents & POLLIN) //★変更箇所 受信イベント発生 { iClntLen = sizeof(ClntAddr); // 接続先アドレス情報を格納する構造体のサイズ if ((m_fdClient = accept(m_fdServer[ii], (sockaddr *)&ClntAddr, &iClntLen)) == INVALID_SOCKET) DispErrorMsg(“Err:accept”); else { if (getnameinfo((struct sockaddr *) &ClntAddr, (socklen_t)iClntLen, szHostAddr, sizeof(szHostAddr), NULL, 0, NI_NUMERICHOST) == 0) { fprintf(stderr, “%s\n”, szHostAddr); } fRet = TRUE; goto L_END; } } } } L_END: return(fRet); }

【SimpleServer.cpp】 //////////////////////////////////////////////// // function // 受信処理 // parameter // なし // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DoRecv() { BOOL fRet = TRUE; char szRecvBuffer[RCVBUFSIZE + 1]; // 受信バッファ int iSize; pollfd fds[1] = {0}; //★変更箇所 fprintf(stderr, “DoRecv()\n”); fds[0].fd = m_fdClient; fds[0].events = POLLIN | POLLRDHUP; //★変更箇所 受信と相手側からの切断イベントを設定 while (1) { poll(fds, 1, 10); //★変更箇所 if (CheckKey()) { fprintf(stderr, ” OK:Abort by key\n”); break; } if (fds[0].revents & POLLRDHUP) //★変更箇所 相手側からの切断 { fprintf(stderr, “Disconnected pollrdhup\n”); fRet = FALSE; break; } else if (fds[0].revents & POLLERR) //★変更箇所 エラー発生 { DispErrorMsg(“Err:DoRecv”); fRet = FALSE; break; } else if (fds[0].revents & POLLIN) //★変更箇所 受信データイベント { memset(szRecvBuffer, 0, sizeof(szRecvBuffer)); if ((iSize = recv(m_fdClient, szRecvBuffer, RCVBUFSIZE, 0)) <= 0) { if (iSize == 0) fprintf(stderr, “Disconnected recv\n”); else DispErrorMsg(“Err:recv”); fRet = FALSE; break; } else { fprintf(stderr, “%s\n”, szRecvBuffer); } } } return(fRet); }

次にLinux版SimpleClientの変更を変更します。
SimpleServerと同様、更新済みstdThread.cpp, stdThead.hに置き換えてください。
以下に変更する関数を記述します。(selectに関係している関数)

//////////////////////////////////////////////// // function // sendの実行 // parameter // char *pcData [in]送信データ // int iSize [in]データ数 // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DoSend(char *pcData, int iSize) { BOOL fRet = TRUE; pollfd fds[1] = {0}; //★変更箇所 fds[0].fd = m_fdClient; //★変更箇所 fds[0].events = POLLOUT; // 書き込み可能イベントを設定 while (1) { poll(fds, 1, 10); if (fds[0].revents & POLLERR) //★変更箇所 エラー発生 { DispErrorMsg(“Err DoSend”); fRet = FALSE; break; } else if (fds[0].revents & POLLOUT) //★変更箇所 送信可能ならsend実施 { if (send(m_fdClient, pcData, iSize, 0) != iSize) { DispErrorMsg(“Err:send”); fRet = FALSE; } break; } } return(fRet); }

【参考1】
VisualStudioを使ってデバッグをする際のulimitの指定の仕方

【参考2】
ulimit コマンドを実行する場合、ハードリミットを設定したい場合は -H 、
ソフトリミットを設定したい場合は-S のオプションを指定しますが、
どちらも指定しないと両方に制限がかけられます。
ソフトリミットを設定する場合は、ハードリミットの範囲内で設定します。
ハードリミットを設定すると、一般ユーザはハードリミットの範囲内でしか値を
変更する事ができません。
一般ユーザが設定された値より大きな値にシステムリソースの値を変更しようと
した場合には、エラーが表示されます。
設定されているハードリミットより大きな値に変更するためには root 権限が必要です。
再起動後設定値は、初期に戻るので再設定をするか
/etc/security/limits.conf の設定を書き変えます。

プロジェクトSimpleServer for Linux poll対応

プロジェクト SimpleClient for Linux poll対応