「ファイル」→「新規作成」→「VisualC++」→「空のプロジェクト」
「ソリューションのディレクトリを作成する」のチェックを外します。
「場所」は各自が読み書きできるフォルダを指定します。
「名前」はSimpleClientとします。
プロジェクト→既存の項目の追加を選びstdThread.cpp, stdThread.hを追加します。
プロジェクト→新規の項目の追加を選びSimpleClient.cppを追加します。
プロジェクト→プロパティの構成プロパティの文字セットが「マルチ バイト文字セットを
使用する」になっていることを確認してください。
プラットフォームは64bits(x64)にしましょう。
上図の関数をmainで順番に呼び出します。
このプログラムは、まだマルチスレッド化していません。
【SimpleClient.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 CMD_QUIT_CHAR ‘q’ #define CMD_SEND_MSG_CHAR ‘m’ // 関数の宣言 BOOL InitSocketLib(); // WinSockDLLの初期化 BOOL UninitSocketLib(); // WinSockDLLの終了 BOOL CreateAndConnectSocket(LPCSTR szAddress, WORD wPort); // ソケットの作成と接続処理 BOOL DoSend(char *pcData, int iSize); // 送信処理の実行 BOOL DestroySocket(SOCKET &fd); // 切断とソケットの破棄 void Stop(); // すべてのソケットを破棄する void DispMenu(); // メニューの表示 int GetKeyString(LPSTR pszString, int iSize); // キーボード入力文字の取得 // 変数の宣言 SOCKET m_fdClient; // 接続、通信用ソケット int main(int argc, char *argv[]) { char szKeyInBuff[81]; int iSize; LPBYTE pbDest = NULL; m_fdClient = INVALID_SOCKET; // 起動パラメータチェック if (argc != 3) { fprintf(stderr, “Usage: %s <ServerAddress> <ServerPort>\n”, argv[0]); goto L_END; } // ソケットライブラリの初期化 if (InitSocketLib() == FALSE) goto L_END; // ソケットの作成と接続処理 // m_fdClientが接続済み状態になる if (CreateAndConnectSocket(argv[1], (WORD)atol(argv[2])) == FALSE) goto L_END; // メニューの表示 DispMenu(); while (1) { // キーボードから入力された文字列が’q’なら終了 // ‘m’ならメッセージを入力後は送信する switch (GetKeyString(szKeyInBuff, sizeof(szKeyInBuff))) { case 0: // 入力なし、何もしない break; case CMD_QUIT_CHAR: // ‘q’入力終了 goto L_END; case CMD_SEND_MSG_CHAR: // ‘m’入力szKeyInBuffに文字列が格納されている DoSend(szKeyInBuff, strlen(szKeyInBuff)); // メニューの表示 DispMenu(); break; } } L_END: // 切断とすべてのソケットの破棄 Stop(); // ソケットライブラリの開放 UninitSocketLib(); return(0); }
Windowsのみに必要な、ソケットライブラリの初期化と終了の
ための関数です。
【SimpleClient.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); }
起動引数で指定されたアドレス、ポート番号を指定しgetaddrinfoを呼び出します。
得られた情報を使ってソケットを作成し、接続を要求します。
接続が失敗した時は、次の情報を使ってソケット作成、接続要求を行います。
成功した時は接続済みソケットをm_fdClientに格納します。
【SimpleClient.cpp】 //////////////////////////////////////////////// // function // TCPソケットの作成, connectの実行 // 接続成功したときソケットはm_fdClientに格納する // parameter // LPCSTR szAddress [in]接続先アドレス // WORD wPort [in]接続先ポート // return // TRUE/FALSE //////////////////////////////////////////////// BOOL CreateAndConnectSocket(LPCSTR szAddress, WORD wPort) { BOOL fRet = TRUE; SOCKET fd; struct addrinfo hints, *pres = NULL, *pTemp = NULL; char szPort[NI_MAXSERV]; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; // TCP DISABLE_C4996 sprintf(szPort, “%d”, wPort); // ポート番号=サービス ENABLE_C4996 // アドレスを指定してgetaddrinfoを実行 接続先アドレス情報を得る if (getaddrinfo(szAddress, szPort, &hints, &pres) == 0) { pTemp = pres; while (pTemp != NULL) { // socket, connectを実行 if ((fd = socket(pTemp->ai_family, pTemp->ai_socktype, pTemp->ai_protocol)) == INVALID_SOCKET) goto L_NEXT; if (connect(fd, pTemp->ai_addr, (int)pTemp->ai_addrlen) == SOCKET_ERROR) { fprintf(stderr, “Err:connect %d\n”, WSAGetLastError()); DestroySocket(fd); goto L_NEXT; } // 接続成功したのでこのソケット採用することにして抜ける fRet = TRUE; break; L_NEXT: pTemp = pTemp->ai_next; } freeaddrinfo(pres); } if (fd == INVALID_SOCKET) // 接続に失敗 { fprintf(stderr, “Err:connect %d\n”, WSAGetLastError()); fRet = FALSE; goto L_END; } m_fdClient = fd; // 接続済み状態のソケットをm_fdClientに格納 L_END: return(fRet); }
渡されたデータをソケットに送信します。
ここでは、送信サイズ(sendに渡せるサイズ)についてチェックをしていません。
送信可能サイズを調べるべきですが、キーボードからの
入力ということで、それほど大きくないということで省略しています。
(getsockoptにパラメータSO_MAX_MSG_SIZEを指定して呼び出せば調べられます)
【SimpleClient.cpp】 //////////////////////////////////////////////// // function // sendの実行 // parameter // char *pcData [in]送信データ // int iSize [in]データ数 // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DoSend(char *pcData, int iSize) { BOOL fRet = TRUE; fd_set wfds; struct timeval tv; while (1) { tv.tv_sec = 0; tv.tv_usec = 10 * 1000; // 10msec FD_ZERO(&wfds); FD_SET(m_fdClient, &wfds); // 送信可能か監視 select(FD_SETSIZE, NULL, &wfds, NULL, &tv); // タイムアウトまでSleepと同等 if (FD_ISSET(m_fdClient, &wfds)) // 送信可能ならsend実施 { if (send(m_fdClient, pcData, iSize, 0) != iSize) { fprintf(stderr, “Err:send %d\n”, WSAGetLastError()); fRet = FALSE; } break; } } return(fRet); }
ソケットを破棄する関数です。
DestroySocketは指定したソケットを破棄します。
Stopはすべてのソケットを破棄します。
【SimpleClient.cpp】 //////////////////////////////////////////////// // function // TCPソケットの破棄 // parameter // SOCKET &fd[in/out]破棄するソケット // return // TRUE/FALSE //////////////////////////////////////////////// BOOL DestroySocket(SOCKET &fd) { if (fd != INVALID_SOCKET) { fprintf(stderr, “DestroySocket()\n”); 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); }
ソケットとは関係のない、メニューを表示する関数とキーボードからの
入力を調べる関数です。
_kbhitでキーの押下をチェックをし、押された文字によって動作を
変えています。’m’が押されたときは文字列の入力を待ちます。
これらの処理はWindows独自ですのでLinux版の時には違う方法を
考えることにします。
【SimpleClient.cpp】 //////////////////////////////////////////////// // function // メニューの表示 // parameter // なし // return // なし //////////////////////////////////////////////// void DispMenu() { fprintf(stderr, “%c:quit %c:メッセージ送信 : “, CMD_QUIT_CHAR, CMD_SEND_MSG_CHAR); } //////////////////////////////////////////////// // function // キーボード入力(改行まで)の取得 // 入力がないときはすぐにリターン // parameter // LPSTR pszString [in/out]データ格納用バッファ // int iSize [in]バッファのサイズ // return // 0:入力なし CMD_QUIT_CHAR:終了 CMD_SEND_MSG_CHAR:メッセージ送信 //////////////////////////////////////////////// int GetKeyString(LPSTR pszString, int iSize) { int iRet = 0; memset(pszString, 0, iSize); if (_kbhit()) { switch (_getch()) { case CMD_QUIT_CHAR: iRet = CMD_QUIT_CHAR; break; case CMD_SEND_MSG_CHAR: fprintf(stderr, “\n送信メッセージを入力してください : “); fgets(pszString, iSize, stdin); if (strlen(pszString) > 0) { // 改行まで読み込んでいるので if (pszString[strlen(pszString) – 1] == ‘\n’) pszString[strlen(pszString) – 1] = 0; iRet = CMD_SEND_MSG_CHAR; } break; default: break; } } return(iRet); }
これでSimpleClient.cppが完成です。
ビルドして動かしてみてください。
折角ですので、サーバ側はSimpleServerを使いましょう。
ポートは10000くらいを使ってみましょう。
次の組み合わせで動作を確認しましょう。
SimpleServer Windows <==== SimpleClient Windows
SimpleServer Linux <==== SimpleClient Windows
文字列はちゃんと送信受信できるでしょうか。
日本語(全角文字)の送受信が、SimpleServer Linux >==== SimpleClient Windows
ではうまくいっていません。
これは、LinuxとWindowsでは漢字の扱い(文字コード)が違うためです。
Windowsは「マルチバイト文字セットを使用する」としたのでSift-JIS
LinuxはUTF-8
ネットワークプログラミングでは、通信する相手同士で色々約束をきめなければ
正しく、通信(会話)することができません。
同じレイヤ(今の場合アプリケーション同士)での約束事をプロトコルと言います。
今回のアプリケーションでの文字列については、
・文字コードとしてはUTF-8を使う
・改行コードはLF(\n)を使う
という約束(プロトコル)とします。
コード変換については、SimpleClient for Linuxを作った後に対応することにします。
(*)WindowsでUnicodeと指定してもUTF-16ですので、変換が必要です。