「ファイル」→「新規作成」→「VisualC++」→「空のプロジェクト」
「ソリューションのディレクトリを作成する」のチェックを外します。
「場所」は各自が読み書きできるフォルダを指定します。
「名前」はSimpleServerとします。
プロジェクト→既存の項目の追加を選びstdThread.cpp, stdThread.hを追加します。
プロジェクト→新規の項目の追加を選びSimpleServer.cppを追加します。
プロジェクト→プロパティの構成プロパティの文字セットが「マルチ バイト文字セットを
使用する」になっていることを確認してください。
プラットフォームは64bits(x64)にしましょう。
上図の関数をmainで順番に呼び出します。
このプログラムは、まだマルチスレッド化していないので
接続要求への対応は1接続のみで、その接続要求を受容後は
他の接続に対しての処理をおこなうことはできません。
【SimpleServer.cpp】 #include “stdThread.h” #include <conio.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 #define MAXPENDING (5) // 接続バックログ数(保留中の接続のキューの最大長) #define RCVBUFSIZE (1024) // 一回に読む最大受信サイズ #define MAX_SOCKET_NUM (20) // 接続待用ソケットの最大数 // 関数の宣言 BOOL InitSocketLib(); // WinSockDLLの初期化 BOOL UninitSocketLib(); // WinSockDLLの終了 BOOL CreateAndBindSocket(WORD wPort); // 接続待ち用ソケットの作成と名前付け BOOL DestroySocket(SOCKET &fd); // 切断とソケットの破棄 BOOL DoListen(); // ソケットを接続待ちにする BOOL DoAccept(); // 接続の受容(接続済みソケットを作成する) BOOL DoRecv(); // 受信処理の実行 void Stop(); // すべてのソケットを破棄する BOOL CheckKey(); // キー入力の検査 // 変数の宣言 SOCKET m_fdServer[MAX_SOCKET_NUM]; // 待機用ソケット int m_iSockCount; // 待機中ソケットの数 SOCKET m_fdClient; // 接続済みソケット(クライアントとの通信用) int main(int argc, char &argv[]) { int iRet = -1; int ii; // 起動パラメータチェック if (argc != 2) { fprintf(stderr, “Usage: %s <Server Port>\n”, argv[0]); goto L_END; } // ソケットライブラリの初期化 if (InitSocketLib() == FALSE) goto L_END; // 変数の初期化 for (ii = 0; ii < MAX_SOCKET_NUM; ++ii) m_fdServer[ii] = INVALID_SOCKET; m_fdClient = INVALID_SOCKET; // 接続待ちソケットの作成と名前付け if (CreateAndBindSocket((WORD)atol(argv[1])) == FALSE) goto L_END; // 接続待ち状態にする if (DoListen() == FALSE) goto L_END; // 接続要求に対して接続済みソケットを作成する if (DoAccept() == FALSE) goto L_END; // 受信の実行 if (DoRecv() == FALSE) goto L_END; iRet = 0; L_END: // 切断とすべてのソケットの破棄 Stop(); // ソケットライブラリの開放 UninitSocketLib(); return(iRet); }
Windowsのみに必要な、ソケットライブラリの初期化と終了の
ための関数です。
【SimpleServer.cpp】 // 必要とするWINSOCK.DLL(WSOCK32.DLL のバージョン) #define VERSION_RECESTED MAKEWORD(2, 0) //////////////////////////////////////////////// // function // ソケットライブラリのロード // parameter // なし // retun // なし //////////////////////////////////////////////// BOOL InitSocketLib() { WORD wVersionRequested; // バージョン情報格納エリア WSADATA wsaData; // WinSock 情報格納エリア BOOL fRet; wVersionRequested = VERSION_RECESTED; // 初期化処理 fRet = (WSAStartup(wVersionRequested, &wsaData) == 0) ? TRUE : FALSE; return(fRet); } //////////////////////////////////////////////// // function // ソケットライブラリの開放 // parameter // なし // retun // なし //////////////////////////////////////////////// BOOL UninitSocketLib() { BOOL fRet; fRet = (WSACleanup() == 0) ? TRUE : FALSE; // 解放処理 return(fRet); }
接続待機のためのソケットをflagにAI_PASSIVEを指定しで得られた
待機を行うアドレス情報の分だけ作成します。
作成したソケットに名前(ポート番号)を付けます。
IPv4, IPv6の両方のソケットを作成しますので、IPv4射影アドレスは
使用しないよう設定します。
プログラムを繰り返し動かすときのためにSO_REUSEADDRを指定して
TIME_WAIT状態の時にbindで失敗しないようにしています。
この関数は、bind出来たソケットが1つもないときにFALSEを返します。
【SimpleServer.cpp】 //////////////////////////////////////////////// // function // 待機用ソケットの作成とbindの実施 // parameter // WORD wPort [in]ポート番号 // return // TRUE/FALSE //////////////////////////////////////////////// BOOL CreateAndBindSocket(WORD wPort) { BOOL fRet = TRUE; struct addrinfo hints, *pres = NULL, *pTemp = NULL; char szPort[NI_MAXSERV]; int on; fprintf(stderr, “CreateAndBindSocket()\n”); m_iSockCount = 0; // ストリーム型で待機可能なアドレス情報の条件 memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; DISABLE_C4996 sprintf(szPort, “%d”, wPort); ENABLE_C4996 if (getaddrinfo(NULL, szPort, &hints, &pres) == 0) { // 順番にpresの中に格納されている情報を使用する pTemp = pres; while ((pTemp != NULL) && (m_iSockCount < MAX_SOCKET_NUM)) { // Socketの作成, bindの実行 // ソケット作成 m_fdServer[m_iSockCount] = socket(pTemp->ai_family, pTemp->ai_socktype, pTemp->ai_protocol); if (m_fdServer[m_iSockCount] == INVALID_SOCKET) goto L_NEXT; fprintf(stderr, “%d %d %d %d\n”, (int)m_fdServer[m_iSockCount], pTemp->ai_family, AF_INET, AF_INET6); #ifdef IPV6_V6ONLY // IPv6ソケットでIPv4射影アドレスを使用しないように設定 if (pTemp->ai_family == AF_INET6) { on = 1; setsockopt(m_fdServer[m_iSockCount], IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on, sizeof(on)); } #endif // クローズ直後に際bindできない状態の解消 on = 1; setsockopt(m_fdServer[m_iSockCount], SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); // bindの実行 if (bind(m_fdServer[m_iSockCount], pTemp->ai_addr, (int)pTemp->ai_addrlen) == SOCKET_ERROR) { // bindに失敗したら、そのソケットを破棄して次のソケットの処理を行う fprintf(stderr, ” Err:bind %d\n”, WSAGetLastError()); DestroySocket(m_fdServer[m_iSockCount]); goto L_NEXT; } ++m_iSockCount; L_NEXT: pTemp = pTemp->ai_next; // 次の情報を処理する } freeaddrinfo(pres); } if (m_iSockCount == 0) // ひとつもbindできなかったときは失敗を返す fRet = FALSE; return(fRet); }
接続待機のために作成したソケットを接続待機状態にします。
【SimpleServer.cpp】 //////////////////////////////////////////////// // function // 待機用ソケットを接続待ち状態にする // parameter // なし // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DoListen() { BOOL fRet = FALSE; int ii; fprintf(stderr, “DoListen()\n”); for (ii = 0; ii < m_iSockCount; ++ii) { if (listen(m_fdServer[ii], MAXPENDING) == SOCKET_ERROR) continue; fRet = TRUE; } return(fRet); }
接続待機状態のソケットに接続要求があったとき、接続要求キューの
処理を行い、接続済みソケットを作成します。
作成した接続済みソケットはm_fdClientに格納し、受信処理に使用します。
この関数は、誰かが接続しにくるか、キーボード入力がある(中断要求)まで
whileによるループを繰り返します。
acceptはブロッキング関数なので、接続要求キューの検査をselectで行い
要求があったときのみacceptを呼ぶようにしています。
acceptが成功した時、相手のアドレス情報は大きな構造体sockaddr_storageに
格納していることに注意してください。
【SimpleServer.cpp】 //////////////////////////////////////////////// // function // 接続要求の受け入れ(接続済みソケットの作成) // parameter // なし // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DoAccept() { BOOL fRet = FALSE; sockaddr_storage ClntAddr; // 接続クライアントのアドレス情報 socklen_t iClntLen; // 接続クライアントのアドレス情報のサイズ char szHostAddr[NI_MAXHOST]; int ii; fd_set rfds; struct timeval tv; fprintf(stderr, “DoAccept()\n”); while (1) { tv.tv_sec = 0; tv.tv_usec = 10 * 1000;// 10msec FD_ZERO(&rfds); for (ii = 0; ii < m_iSockCount; ++ii) { FD_SET(m_fdServer[ii], &rfds); // bind成功したm_fdServer[ii]が調査対象 } select(FD_SETSIZE, &rfds, NULL, NULL, &tv); // 接続要求は読み込みで検査する // キー入力で中断 if(CheckKey()) { fprintf(stderr, ” OK:Abort by key\n”); break; } for (ii = 0; ii < m_iSockCount; ++ii) { if (FD_ISSET(m_fdServer[ii], &rfds)) // 接続要求があったときacceptを実施 { iClntLen = sizeof(ClntAddr); // 接続先アドレス情報を格納する構造体のサイズ if ((m_fdClient = accept(m_fdServer[ii], (sockaddr *)&ClntAddr, &iClntLen)) == INVALID_SOCKET) { // 失敗時次の接続要求を処理する fprintf(stderr, “Err:accept %d\n”, WSAGetLastError()); continue; } else { // 成功時m_fdClientには接続済みのソケットが格納されている // 相手の情報から、IPアドレスを調べる if (getnameinfo((struct sockaddr *) &a,p;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); }
接続済みソケットm_fdClientを使って受信処理を行います。
切断を検知(0バイトの受信)またはキーボード入力による中断が
発生するまでwhileによるループを繰り返します。
recvはブロッキング関数なので、TCPスタックの受信バッファの検査をselectで
行い受信があったときのみrecvtを呼ぶようにしています。
ここでは、受信したデータは文字列として、表示をするだけです。
そのためにszRecvBufferのサイズより1バイト少なくrecvを呼びNULLターミネート
文字列として表示しています。
【SimpleServer.cpp】 //////////////////////////////////////////////// // function // 受信処理 // parameter // なし // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DoRecv() { BOOL fRet = TRUE; char szRecvBuffer[RCVBUFSIZE + 1]; // 受信バッファ int iSize; fd_set rfds; struct timeval tv; fprintf(stderr, “DoRecv()\n”); while (1) { tv.tv_sec = 0; tv.tv_usec = 10 * 1000; // 10msec FD_ZERO(&rfds); FD_SET(m_fdClient, &rfds); select(FD_SETSIZE, &rfds, NULL, NULL, &tv); // タイムアウトまでSleepと同等 // キー入力で中断 if (CheckKey()) { fprintf(stderr, ” OK:Abort by key\n”); break; } if (FD_ISSET(m_fdClient, &rfds)) { memset(szRecvBuffer, 0, sizeof(szRecvBuffer)); if ((iSize = recv(m_fdClient, szRecvBuffer, RCVBUFSIZE, 0)) <= 0) { if (iSize == 0) // 0バイトの受信は切断検知 -1はエラー fprintf(stderr, “Disconnected %d\n”, WSAGetLastError()); else fprintf(stderr, “Err:recv %d\n”, WSAGetLastError()); fRet = FALSE; break; } else { fprintf(stderr, “%s\n”, szRecvBuffer); } } } return(fRet); }
ソケットを破棄する関数です。
DestroySocketは指定したソケットを破棄します。
Stopはすべてのソケットを破棄します。
【SimpleServer.cpp】 //////////////////////////////////////////////// // function // TCPソケットの破棄 // parameter // SOCKET &fd[in/out]破棄するソケット // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DestroySocket(SOCKET &fd) { fprintf(stderr, “DestroySocket()\n”); if (fd != INVALID_SOCKET) { shutdown(fd, SD_BOTH); // 受信も送信も停止 closesocket(fd); fd = INVALID_SOCKET; } return(TRUE); } //////////////////////////////////////////////// // function // すべてのソケットの切断、破棄 // parameter // なし // return // なし //////////////////////////////////////////////// void Stop() { int ii; fprintf(stderr, “Stop()\n”); DestroySocket(m_fdClient); for (ii = 0; ii < m_iSockCount; ++ii) DestroySocket(m_fdServer[ii]); }
ソケットとは関係ありませんが、キーボードからの入力を検査する 関数です。
Windows独特の関数です。Linuxでは、標準入力のファイルディスクリプタを
ソケットのselectによる検査と同様に扱う関数で作ります。
【SimpleServer.cpp】 //////////////////////////////////////////////// // function // キー入力チェック // parameter // なし // return // TRUE:入力あり //////////////////////////////////////////////// BOOL CheckKey() { BOOL fRet = FALSE; if (_kbhit()) { _getch(); fRet = TRUE; } return(fRet); }
これでSimpleServer.cppが完成です。
ビルドしてみてください。
引数にポート番号を指定して動かしてみます。
下の図はポート番号に10000を指定して動かし、同じPCでIEでもポート番号10000を
指定して接続してみたところです。
IEのアドレスにIPv4を指定した時、http://127.0.0.1:10000/
C:\SimpleServer\x64\Debug>SimpleServer 10000 CreateAndBindSocket() 176 23 2 23 ←待機用IPv6のソケット 180 2 2 23 ←待機用IPv4のソケット DoListen() DoAccept() 127.0.0.1 ←IPv4のローカルホストからつなぎに来た DoRecv() GET / HTTP/1.1 ←IEが送信してきた文字列 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134 Accept-Language: ja,zh-Hans-CN;q=0.7,zh-Hans;q=0.3 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Upgrade-Insecure-Requests: 1 Accept-Encoding: gzip, deflate Host: 127.0.0.1:10000 Connection: Keep-Alive ←キーボードにより中断を要求 OK:Abort by key Stop() DestroySocket() DestroySocket() DestroySocket()
IEのアドレスにIPv6を指定した時、http://[::1]:10000/
C:\SimpleServer\x64\Debug>SimpleServer 10000 CreateAndBindSocket() 188 23 2 23 ←待機用IPv6のソケット 196 2 2 23 ←待機用IPv4のソケット DoListen() DoAccept() ::1 ←IPv6のローカルホストからつなぎに来た DoRecv() GET / HTTP/1.1 ←IEが送信してきた文字列 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134 Accept-Language: ja,zh-Hans-CN;q=0.7,zh-Hans;q=0.3 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Upgrade-Insecure-Requests: 1 Accept-Encoding: gzip, deflate Host: [::1]:10000 Connection: Keep-Alive OK:Abort by key Stop() DestroySocket() DestroySocket() DestroySocket()
【参考】
プロジェクトSimpleServer for Windows(zip)