SimpleClient送受信部分をマルチスレッド対応し、送受信実行中でも
他の処理ができるようにします。
前回までに学習したCThreadJob, CMySyncOblectを使用することでマルチスレッドの
実装を簡単にできます。

SimpleServerMを作成した時と同様にSimpleClientUTF8のプロジェクトをコピーし、
SimpleClientMに名前変更します。
CThreadJob, CMySyncObjectを追加します。
stdThread.h, stdThraed.cppを更新します。

次のクラスを追加します。
このクラスにDoSend()の機能を実装します。
クラス名:CSendRecvThread
ファイル名:SendRecvThread.h, SendRecvThread.cpp
基底クラス:CThreadJob
これもSimpleServerMでやりましたね。

CSendRecvThreadにメインスレッドから渡す必要のある情報は
connectを実行後の接続済みソケットなので、これを含む構造体ConnectionInfoRecを
定義します。
送信したいデータを格納するエリア、送信したいデータのサイズのメンバ変数(*)
送信したいデータをセットする関数、取得する関数を追加します。
(*)送信データの受け渡しには、リングバッファなどを使うのが良いのですが
今回は、常にバッファ全体を更新することにしています。
リングバッファについては後程、実装することにします。

【SendRecvThread.h】 #pragma once #include “ThreadJob.h” class CMySyncObject; // このクラスの使用することを宣言 typedef struct { SOCKET fdClient; // 接続済みソケット(connectの結果) CMySyncObject *pCMySyncObject; // 同期オブジェクト } ConnectionInfoRec; class CSendRecvThread : public CThreadJob { public: CSendRecvThread(ConnectionInfoRec *pConInfo); // パラメータをコンストラクタで渡す ~CSendRecvThread(); // 基底クラスの関数をオーバーライドする // C++11で明示的にoverrideを書くことが出来るようになりました // 基底クラスの当該関数にvirtualが書いていないとエラーを出してくれます UINT DoWork() override; // DoSendで実施している内容を記述 BOOL SetSendData(char *pcData, int iSize); // ★送信データの設定 BOOL IsZombie(); // このスレッドはゾンビ状態か private: ConnectionInfoRec *m_pConInfo; // コンストラクタで渡されるパラメータを格納 // このスレッド実行中領域が確保されていること BOOL m_fIamZombie; // ゾンビ状態かどうかを保持 char *m_pSendData; // ★送信データ格納用エリア int m_iSendDataSize; // ★送信データ格納用エリアのデータサイズ int GetSendData(char **ppcData); // ★送信データの取得 };

・コンストラクタでは、送信したいデータを格納するための変数の初期化を行います。
・デストラクタでは、これの開放を行います。
・DoWork()にDoSend()で行っている処理を記述しています。
whileループから抜けて実質的にこのスレッドが不要になったときには
ゾンビ状態になったことを示す変数をTRUEにします。
未送信のデータがなければ、送信したいデータがあるかどうか調べます。
未送信データがあるときだけ、送信可能検査のためにFD_SETを実施するようにします。

【SendRecvThread.cpp】 #include “SendRecvThread.h” #include “MySyncObject.h” // CMySyncObjectを使うため //////////////////////////////////////////////// // function // コンストラクタ // parameter // ConnectionInfoRec *pConInfo [in]機能に必要な情報 // return // なし //////////////////////////////////////////////// CSendRecvThread::CSendRecvThread(ConnectionInfoRec *pConInfo) { m_pConInfo = pConInfo; m_fIamZombie = FALSE; m_pSendData = NULL; // ★送信したいデータを格納するエリア m_iSendDataSize = 0; // ★送信したいデータのサイズ } //////////////////////////////////////////////// // function // デストラクタ // parameter // なし // return // なし //////////////////////////////////////////////// CSendRecvThread::~CSendRecvThread() { SAFE_FREE(m_pSendData) // ★送信したいデータを格納するエリアを開放します m_iSendDataSize = 0; } //////////////////////////////////////////////// // function // 機能を記述した関数(DoSendに記述してあった内容を記述する) // parameter // なし // return // 0:正常 -1:エラー発生 //////////////////////////////////////////////// DWORD CSendRecvThread::DoWork() { BOOL fRet = TRUE; fd_set wfds; struct timeval tv; char *pcData = NULL; // ★未送信データ int iSendSize = 0; // ★未送信データサイズ while (!m_fStopFlag) { // ★未送信のデータがなければ送信したいデータがあるか調べる if (iSendSize == 0) iSendSize = GetSendData(&pcData); tv.tv_sec = 0; tv.tv_usec = 10 * 1000; // 10msec FD_ZERO(&wfds); // ★未送信データがあるときだけ送信可能検査用fd_setにセットする if (iSendSize > 0) FD_SET(m_pConInfo->fdClient, &wfds); select(FD_SETSIZE, NULL, &wfds, NULL, &tv); // タイムアウトまでSleepと同等 if (FD_ISSET(m_pConInfo->fdClient, &wfds)) // 送信可能ならsend実施 { if (send(m_pConInfo->fdClient, pcData, iSendSize, 0) != iSendSize) { DispErrorMsg(“Err:send”); fRet = FALSE; break; } SAFE_FREE(pcData) // ★未送信データなしにセット iSendSize = 0; } } m_pConInfo->pCMySyncObject->Lock(); m_fIamZombie = TRUE; m_pConInfo->pCMySyncObject->UnLock(); SAFE_FREE(pcData) iSendSize = 0; return((fRet == TRUE) ? 0 : -1); } //////////////////////////////////////////////// // function // ★送信データの設定 // parameter // char *pcData [in]送信データ // int iSize [in]データ長 // return // TRUE/FALSE //////////////////////////////////////////////// BOOL CSendRecvThread::SetSendData(char *pcData, int iSize) { BOOL fRet = FALSE; m_pConInfo->pCMySyncObject->Lock(); // 前回のデータを読みだしていないときはエラーにする if (m_iSendDataSize == 0) { m_pSendData = (char *)calloc(iSize, sizeof(char)); memcpy(m_pSendData, pcData, iSize); m_iSendDataSize = iSize; fRet = TRUE; } m_pConInfo->pCMySyncObject->UnLock(); return(fRet); } //////////////////////////////////////////////// // function // ★送信データの取得 // parameter // char **ppcData [in/out]送信データ // return // データ長 //////////////////////////////////////////////// int CSendRecvThread::GetSendData(char **ppcData) { int iSize = 0; m_pConInfo->pCMySyncObject->Lock(); if (m_iSendDataSize > 0) { *ppcData = (char *)calloc(m_iSendDataSize, sizeof(char)); memcpy(*ppcData, m_pSendData, m_iSendDataSize); iSize = m_iSendDataSize; SAFE_FREE(m_pSendData) // 読み出しを行ったことをセット m_iSendDataSize = 0; // 送信したいデータのエリアをクリアする } m_pConInfo->pCMySyncObject->UnLock(); return(iSize); } //////////////////////////////////////////////// // function // このスレッドはゾンビ状態か // 別スレッドから参照される // parameter // なし // return // 0:正常 -1:エラー発生 //////////////////////////////////////////////// BOOL CSendRecvThread::IsZombie() { BOOL fRet; m_pConInfo->pCMySyncObject->Lock(); fRet = m_fIamZombie; m_pConInfo->pCMySyncObject->UnLock(); return(fRet); }

SetSendDataをみると、送信したいデータをm_pSendDataにサイズをm_iSendDataSizeにセット
していますが、前回のデータを読みだしていないときは、エラーにしています。
つまり、セットできるデータは1組です。これで実用的なプログラムを考えるときには不十分
ですので、後程にリングバッファを使ったものに変更することにします。

メインスレッド(SimpleClient.cpp)では以下の変更を行います。
1.DoSendは送受信スレッドに移動したので削除します。
もちろん関連の変数(m_fdClientなど)も削除します。
2.送信はSetSendData関数を呼び出して送信データをセットするだけです。
 実際の送信は送受信スレッドが行ってくれます。
3.connectが成功したら送受信スレッド(CSendRecvThread)を開始します。
4.ゾンビ状態のスレッドは破棄します。

次の関数には変更がありません。
BOOL InitSocketLib()
BOOL UninitSocketLib()
BOOL DestroySocket(SOCKET &fd)
void DispMenu()
int GetKeyString(LPSTR pszString, int iSize)

不要になったので削除した関数は次のものです。
BOOL DoSend()

【SimpleClient.cpp】 #include “stdThread.h” #include <conio.h> // キー入力検査のため #include “SendRecvThread.h” // ★送受信スレッドを使うため #include “MySyncObject.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 DestroySocket(SOCKET &fd); // 切断とソケットの破棄 void Stop(); // すべてのソケットを破棄する void DispMenu(); // メニューの表示 int GetKeyString(LPSTR pszString, int iSize); // キーボード入力文字の取得 BOOL KillZombei(); // ★ゾンビ状態のスレッドを破棄する // 変数の宣言 CSendRecvThread *m_pCSendRecvThread = NULL; // ★ ConnectionInfoRec *m_pConInfo = NULL; // ★ CMySyncObject *m_pCMySyncObject = NULL; // ★ int main(int argc, char *argv[]) { char szKeyInBuff[81]; #if 0 #else int iSize; LPBYTE pbDest = NULL; #endif m_pCMySyncObject = new CMySyncObject(); // ★ m_pCMySyncObject->Initialize(); // ★ // 起動パラメータチェック if (argc != 3) { fprintf(stderr, “Usage: %s <ServerAddress> <ServerPort>\n”, argv[0]); goto L_END; } // ソケットライブラリの初期化 if (InitSocketLib() == FALSE) goto L_END; // ソケットの作成と接続処理 // ★送受信スレッドを開始する if (CreateAndConnectSocket(argv[1], (WORD)atol(argv[2])) == FALSE) goto L_END; // メニューの表示 DispMenu(); while (1) { if (KillZombei() == TRUE) // ★切断してたら終了 break; // キーボードから入力された文字列が’q’なら終了 // ‘m’ならメッセージを入力後は送信する switch (GetKeyString(szKeyInBuff, sizeof(szKeyInBuff)- 1)) { case 0: // 入力なし、何もしない break; case CMD_QUIT_CHAR: // ‘q’入力終了 goto L_END; case CMD_SEND_MSG_CHAR: // ‘m’入力szKeyInBuffに文字列が格納されている #if 0 DoSend(szKeyInBuff, (int)strlen(szKeyInBuff)); #else // S-JISをUTF-8に変換して送信 iSize = 0; pbDest = NULL; ConvSJistoUtf8((LPBYTE)szKeyInBuff, NULL, &iSize); pbDest = (LPBYTE)calloc(iSize, sizeof(BYTE)); ConvSJistoUtf8((LPBYTE)szKeyInBuff, pbDest, &iSize); // ★送信は送信データをセットするだけ、実際の送信はCSendRecvThreadで実施 m_pCSendRecvThread->SetSendData((char *)pbDest, iSize); SAFE_FREE(pbDest) #endif // メニューの表示 DispMenu(); break; } } L_END: // 切断とすべてのソケットの破棄 Stop(); // ソケットライブラリの開放 UninitSocketLib(); m_pCMySyncObject->Uninitialize(); // ★ SAFE_DELETE(m_pCMySyncObject) // ★ return(0); } #define VERSION_RECESTED MAKEWORD(2, 0) // 必要とするWINSOCK.DLL(WSOCK32.DLL のバージョン) //////////////////////////////////////////////// // function // ソケットライブラリのロード // parameter // なし // retun // なし //////////////////////////////////////////////// BOOL InitSocketLib() { WORD wVersionRequested; // バージョン情報格納エリア(windows) WSADATA wsaData; // WinSock 情報格納エリア(windows) 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); } //////////////////////////////////////////////// // function // TCPソケットの作成, connectの実行 // 接続成功したとき送受信スレッドを開始する // 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) { DispErrorMsg(“Err:connect”); DestroySocket(fd); goto L_NEXT; } // 接続成功したのでこのソケット採用することにして抜ける fRet = TRUE; break; L_NEXT: pTemp = pTemp->ai_next; } freeaddrinfo(pres); } if (fd == INVALID_SOCKET) // 接続に失敗 { DispErrorMsg(“Err:connect”); fRet = FALSE; goto L_END; } // ★送信スレッドを開始する m_pConInfo = (ConnectionInfoRec *)calloc(1, sizeof(ConnectionInfoRec)); m_pConInfo->fdClient = fd; m_pConInfo->pCMySyncObject = m_pCMySyncObject; m_pCSendRecvThread = new CSendRecvThread(m_pConInfo); m_pCSendRecvThread->Begin(); L_END: return(fRet); } //////////////////////////////////////////////// // 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() { fprintf(stderr, “Stop()\n”); if (m_pCSendRecvThread != NULL) // ★ { m_pCSendRecvThread->End(); m_pCSendRecvThread->WaitForEnd(); SAFE_DELETE(m_pCSendRecvThread) DestroySocket(m_pConInfo->fdClient); SAFE_FREE(m_pConInfo) } } //////////////////////////////////////////////// // 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); } //////////////////////////////////////////////// // function // ★切断済みのCSendRecvThreadを破棄する // parameter // なし // return // TRUE:破棄した/FALSE:破棄すべきものがなかった //////////////////////////////////////////////// BOOL KillZombei() { BOOL fRet = FALSE; if (m_pCSendRecvThread != NULL) { if (m_pCSendRecvThread->IsZombie() == TRUE) { m_pCSendRecvThread->End(); m_pCSendRecvThread->WaitForEnd(); SAFE_DELETE(m_pCSendRecvThread); DestroySocket(m_pConInfo->fdClient); SAFE_FREE(m_pConInfo) fRet = TRUE; } } return(fRet); }

これで完成です。作成済みのSimpleServer(Win)を使って、動作確認をしてください。
ということで、次回マルチスレッド対応したSimpleServer, SimpleClientを作って
みましょう。

プロジェクトSimpleClientM for Windows