ここまで、作成したTCP/IPのプログラムを見てみると、サーバには受信機能、
クライアントには送信機能だけが、ありました。
ここでは、サーバ側にも送信機能、クライアント側にも受信機能を追加することにします。
文字通りCSendRecvThredのクラスが送受信機能を持つことになります。
Echoもどきシステム(TCP IPv4 IPv6)を考えてみることにします。
仕様は、以下の通りです。
Clientから送信した文字列(UTF-8)をServerはそのまま送り返します。
Clientはサーバから送り返された文字列を表示します。
先ずは、クライアントを作成します。
作成済みのSimpleClientMのプロジェクトをコピーし、SimpleClientMEchoに名前変更します。
(Windows版、Linux版)
CSendRecvThreadに受信機能を追加します。
これは、SimpleServerMに実装した受信機能を移植すればOKです。
CSendRecvThread(Windows、Linux)のコードは以下のようになります。
【SendRecvThread.cpp Windows変更箇所】 #define RCVBUFSIZE (1024) // ★一回に読む最大受信サイズ //================================================ // function // 機能を記述した関数(DoSend, DoRecvの内容を記述する) // parameter // なし // return // 0:正常 -1:エラー発生 //================================================ UINT CSendRecvThread::DoWork() { BOOL fRet = TRUE; fd_set wfds, rfds; // ★受信用fd_setも準備 struct timeval tv; char *pcData = NULL; // 未送信データ int iSendSize = 0; // 未送信データサイズ char szRecvBuffer[RCVBUFSIZE + 1]; // ★受信バッファ int iRecvSize; // ★受信したサイズ while (!m_fStopFlag) { // 未送信のデータがなければ送信したいデータがあるか調べる if (iSendSize == 0) iSendSize = GetSendData(&pcData); tv.tv_sec = 0; tv.tv_usec = 10 * 1000; // 10msec FD_ZERO(&rfds); // ★受信用fd_setの初期化 FD_ZERO(&wfds); FD_SET(m_pConInfo->fdClient, &rfds); // ★受信データ検査用fd_setは常にセットする if (iSendSize > 0) // 未送信データがあるときだけ送信可能検査用fd_setにセットする FD_SET(m_pConInfo->fdClient, &wfds); select(FD_SETSIZE, &rfds, &wfds, NULL, &tv); // タイムアウトまでSleepと同等 // ★受信処理(受信したらSJISに変換して表示する) if (FD_ISSET(m_pConInfo->fdClient, &rfds)) // 受信データがあればrecv実施 { memset(szRecvBuffer, 0, sizeof(szRecvBuffer)); if ((iRecvSize = recv(m_pConInfo->fdClient, szRecvBuffer, RCVBUFSIZE, 0)) <= 0) { m_pConInfo->pCMySyncObject->Lock(); if (iRecvSize == 0) DispErrorMsg(“Disconnected”); else DispErrorMsg(“Err:recv”); m_pConInfo->pCMySyncObject->UnLock(); fRet = FALSE; break; } else { #if 0 fprintf(stderr, “%s\n”, szRecvBuffer); #else // UTF-8で受信したので、SJISに変換して表示する int iSize = 0; LPBYTE pbDest = NULL; ConvUtf8toSJis((LPBYTE)szRecvBuffer, NULL, &iSize); pbDest = (LPBYTE)calloc(iSize, sizeof(BYTE)); ConvUtf8toSJis((LPBYTE)szRecvBuffer, pbDest, &iSize); m_pConInfo->pCMySyncObject->Lock(); fprintf(stderr, “%s\n”, pbDest); m_pConInfo->pCMySyncObject->UnLock(); SAFE_FREE(pbDest) #endif } } // 送信処理(DoSend) 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); }
【SendRecvThread.cpp Linux変更箇所】 #define RCVBUFSIZE (1024) // ★一回に読む最大受信サイズ //================================================ // function // 機能を記述した関数(DoSend, DoRecvの内容を記述する) // parameter // なし // return // 0:正常 -1:エラー発生 //================================================ UINT CSendRecvThread::DoWork() { BOOL fRet = TRUE; pollfd fds[1] = { 0 }; char *pcData = NULL; // 未送信データ int iSendSize = 0; // 未送信データサイズ char szRecvBuffer[RCVBUFSIZE + 1]; // ★受信バッファ int iRecvSize; // ★受信したサイズ // ★eventsの設定は後述 fds[0].fd = m_pConInfo->fdClient; while (!m_fStopFlag) { // 未送信のデータがなければ送信したいデータがあるか調べる if (iSendSize == 0) iSendSize = GetSendData(&pcData); fds[0].events = POLLIN | POLLRDHUP; // ★受信と相手側からの切断イベントを設定 if (iSendSize > 0) fds[0].events |= POLLOUT; // ★未送信データがあるときは書き込み可能を追加 poll(fds, 1, 10); if (fds[0].revents & POLLERR) // エラー発生 { DispErrorMsg(“Err DoSend”); fRet = FALSE; break; } else if (fds[0].revents & POLLRDHUP) // 切断発生 { DispErrorMsg(“Disconnected”); fRet = FALSE; break; } else if (fds[0].revents & POLLIN) // ★受信データイベント(受信したら表示する) { memset(szRecvBuffer, 0, sizeof(szRecvBuffer)); if ((iRecvSize = recv(m_pConInfo->fdClient, szRecvBuffer, RCVBUFSIZE, 0)) <= 0) { m_pConInfo->pCMySyncObject->Lock(); if (iRecvSize == 0) DispErrorMsg(“Disconnected recv”); else DispErrorMsg(“Err:recv”); m_pConInfo->pCMySyncObject->UnLock(); fRet = FALSE; break; } else { m_pConInfo->pCMySyncObject->Lock(); fprintf(stderr, “%s\n”, szRecvBuffer); m_pConInfo->pCMySyncObject->UnLock(); } } else if (fds[0].revents & POLLOUT) // 送信可能なら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); }
プロジェクトSimpleClientMEcho for Windows
プロジェクトSimpleClientPollMEcho for Linux
次に、サーバを作成します。
作成済みのSimpleServerMのプロジェクトをコピーし、SimpleServerMEchoに名前変更します。
(Windows版、Linux版)
CSendRecvThreadに送信機能を追加します。
これは、Clientに実装した送信機能を移植すればOKです。
受信したデータをそのままSetSendDataで送信データとしてセットします。
SendRecvThread.h、SendRecvThread.cpp(Windows、Linux)のコードは以下のようになります。
【SendRecvThraed.h Windows】 #pragma once #include “ThreadJob.h” class CMySyncObject; // このクラスの使用することを宣言 typedef struct { SOCKET fdClient; // 接続済みソケット(acceptの結果) CMySyncObject *pCMySyncObject; // 同期オブジェクト } ConnectionInfoRec; class CSendRecvThread : public CThreadJob { public: CSendRecvThread(ConnectionInfoRec *pConInfo); // パラメータをコンストラクタで渡す ~CSendRecvThread(); // 基底クラスの関数をオーバーライドする // C++11で明示的にoverrideを書くことが出来るようになりました // 基底クラスの当該関数にvirtualが書いていないとエラーを出してくれます UINT DoWork() override; 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); // ★送信データの取得 };
【SendRecvThraed.cpp Windows】 #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; } #define RCVBUFSIZE (1024) // 一回に読む最大受信サイズ //============================================== // function // 機能を記述した関数(DoRecv, DoSendの内容を記述する) // 受信したデータを送り戻すためにSetSendDataで送信バッファにセットする // parameter // なし // return // 0:正常 -1:エラー発生 //============================================== UINT CSendRecvThread::DoWork() { BOOL fRet = TRUE; char szRecvBuffer[RCVBUFSIZE + 1]; // 受信バッファ int iRecvSize; fd_set rfds, wfds; // ★送信用fd_setを追加 struct timeval tv; char *pcData = NULL; // ★未送信データ int iSendSize = 0; // ★未送信データサイズ fprintf(stderr, “DoWork()\n”); while (!m_fStopFlag) { // ★未送信のデータがなければ送信したいデータがあるか調べる if (iSendSize == 0) iSendSize = GetSendData(&pcData); tv.tv_sec = 0; tv.tv_usec = 10 * 1000; // 10msec FD_ZERO(&rfds); FD_ZERO(&wfds); // ★送信用fd_setの初期化 FD_SET(m_pConInfo->fdClient, &rfds); if (iSendSize > 0) // ★未送信データがあるときだけ送信可能検査用fd_setにセットする FD_SET(m_pConInfo->fdClient, &wfds); select(FD_SETSIZE, &rfds, NULL, NULL, &tv);// タイムアウトまでSleepと同等 // 受信処理(DoRecv) if (FD_ISSET(m_pConInfo->fdClient, &rfds)) { memset(szRecvBuffer, 0, sizeof(szRecvBuffer)); if ((iRecvSize = recv(m_pConInfo->fdClient, szRecvBuffer, RCVBUFSIZE, 0)) <= 0) { m_pConInfo->pCMySyncObject->Lock(); if (iRecvSize == 0) DispErrorMsg(“Disconnected”); else DispErrorMsg(“Err:recv”); m_pConInfo->pCMySyncObject->UnLock(); fRet = FALSE; break; } else { #if 0 fprintf(stderr, “%s\n”, szRecvBuffer); #else // ★受信データ(UTF-8)をそのまま送り返すために同じインスタンスの送信バッファに // データをセットする SetSendData(szRecvBuffer, iRecvSize); // UTF-8で受信したので、SJISに変換して表示する int iSize = 0; LPBYTE pbDest = NULL; ConvUtf8toSJis((LPBYTE)szRecvBuffer, NULL, &iSize); pbDest = (LPBYTE)calloc(iSize, sizeof(BYTE)); ConvUtf8toSJis((LPBYTE)szRecvBuffer, pbDest, &iSize); m_pConInfo->pCMySyncObject->Lock(); fprintf(stderr, “%s\n”, pbDest); m_pConInfo->pCMySyncObject->UnLock(); SAFE_FREE(pbDest) #endif } } // ★送信処理(DoSend) 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); }
Linux版とWindows版との違いはpollとselectだけですのでSendRecvThread.hはWindows版と
全く同じです。
【SendRecvThraed.cpp Linux】 #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; m_iSendDataSize = 0; } #define RCVBUFSIZE (1024) // 一回に読む最大受信サイズ //============================================== // function // 機能を記述した関数(DoRecvに記述してあった内容を記述する) // parameter // なし // return // 0:正常 -1:エラー発生 //============================================== UINT CSendRecvThread::DoWork() { BOOL fRet = TRUE; char szRecvBuffer[RCVBUFSIZE + 1]; // 受信バッファ int iRecvSize; pollfd fds[1] = { 0 }; char *pcData = NULL; // ★未送信データ int iSendSize = 0; // ★未送信データサイズ fprintf(stderr, “DoWork()\n”); // ★eventsの設定は後述 fds[0].fd = m_pConInfo->fdClient; while (!m_fStopFlag) { // ★ 未送信のデータがなければ送信したいデータがあるか調べる if (iSendSize == 0) iSendSize = GetSendData(&pcData); fds[0].events = POLLIN | POLLRDHUP; // ★ 受信と相手側からの切断イベントを設定 if (iSendSize > 0) fds[0].events |= POLLOUT; // ★未送信データがあるとき書き込み可能を追加 poll(fds, 1, 10); if (fds[0].revents & POLLRDHUP) // 相手側からの切断 { fprintf(stderr, “Disconnected pollrdhup\n”); fRet = FALSE; break; } else if (fds[0].revents & POLLERR) // エラー発生 { m_pConInfo->pCMySyncObject->Lock(); DispErrorMsg(“Err:DoWork”); m_pConInfo->pCMySyncObject->UnLock(); fRet = FALSE; break; } else if (fds[0].revents & POLLIN) // 受信データイベント { memset(szRecvBuffer, 0, sizeof(szRecvBuffer)); if ((iRecvSize = recv(m_pConInfo->fdClient, szRecvBuffer, RCVBUFSIZE, 0)) <= 0) { m_pConInfo->pCMySyncObject->Lock(); if (iRecvSize == 0) DispErrorMsg(“Disconnected recv”); else DispErrorMsg(“Err:recv”); m_pConInfo->pCMySyncObject->UnLock(); fRet = FALSE; break; } else { // ★受信データをそのまま送り返すために同じインスタンスの送信バッファにデータをセットする SetSendData(szRecvBuffer, iRecvSize); m_pConInfo->pCMySyncObject->Lock(); fprintf(stderr, “%s\n”, szRecvBuffer); m_pConInfo->pCMySyncObject->UnLock(); } } else if (fds[0].revents & POLLOUT) // 送信可能なら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); }
プロジェクトSimpleServerMEcho for Windows
プロジェクトSimpleServerPollMEcho for Linux
(*)Clientに受信機能を実装したことで、切断検知機能が入ったことを確認してください。
(動作中にServerを止めると、Clientが切断されたことを表示します)