Windows版で行った変更と考え方は同じです。
SimpleClient for Linux poll対応のプロジェクトをコピーし、
SimpleClientPollMに名前変更します。
CThreadJob, CMySyncObjectを追加します。
stdThread.h, stdThraed.cppを更新します。
次のクラスを追加します。
このクラスにDoSend()の機能を実装します。
クラス名:CSendRecvThread
ファイル名:SendRecvThread.h, SendRecvThread.cpp
基底クラス:CThreadJob
【makefile】 CC=g++ -g3 -O0 #CC=g++ PROGRAM=SimpleClientPollM OBJS=SimpleClient.o stdThread.o SendRecvThread.o MySyncObject.o ThreadJob.o SRCS=$(OBJS:%.o=%.cpp) INCLUDE=stdThread.h LFLAGS=-lpthread $(PROGRAM):$(OBJS) $(SRCS) $(INCLUDE) $(CC) -o $(PROGRAM) $(SRCS) $(LFLAGS)
CSendRecvThreadにメインスレッドから渡す必要のある情報は
connectを実行後の接続済みソケットなので、これを含む構造体ConnectionInfoRecを
定義します。
送信したいデータを格納するエリア、送信したいデータのサイズのメンバ変数(*)
送信したいデータをセットする関数、取得する関数を追加します。
(*)送信データの受け渡しには、リングバッファなどを使うのが良いのですが
今回は、常にバッファ全体を更新することにしています。
リングバッファについては後程、実装することにします。
SendRecvThread.hはWindows版と全く同じです。
【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にします。
未送信のデータがなければ、送信したいデータがあるかどうか調べます。
Windows版では未送信データがあるときだけ、送信可能検査のためにFD_SETを実施して
いました。Linux版ではpollを使用するのでこの部分が違います。
pollの仕様をみてみると、pollfdのevents要素が0の場合、reventsで返されるイベントは
POLLHUP, POLLERR, POLLNVAL だけとあります。
したがって次のようにすれば良いことがわかります。
fds[0].events = POLLRDHUP; // POLLRDHUP, POLLHUP, POLLERR, POLLNVALを検査
if (iSendSize > 0) // POLLOUTも検査
fds[0].events |= POLLOUT; // POLLOUTも検査
【SendRecvThead.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:エラー発生 //////////////////////////////////////////////// UINT CSendRecvThread::DoWork() { BOOL fRet = TRUE; pollfd fds[1] = { 0 }; char *pcData = NULL; // 未送信データ int iSendSize = 0; // 未送信データサイズ fds[0].fd = m_pConInfo->fdClient; while (!m_fStopFlag) { // 未送信のデータがなければ送信したいデータがあるか調べる if (iSendSize == 0) iSendSize = GetSendData(&pcData); // 未送信データがあるときのみ送信検査を実施する fds[0].events = POLLRDHUP; // POLLRDHUP, POLLHUP, POLLERR, POLLNVALを検査 if (iSendSize > 0) fds[0].events |= POLLOUT; // 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 & 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); }
メインスレッド(SimpleClient.cpp)では以下の変更を行います。
1.DoSendは送受信スレッドに移動したので削除します。
もちろん関連の変数(m_fdClientなど)も削除します。
2.送信はSetSendData関数を呼び出して送信データをセットするだけです。
実際の送信は送受信スレッドが行ってくれます。
3.connectが成功したら送受信スレッド(CSendRecvThread)を開始します。
4.ゾンビ状態のスレッドは破棄します。
Windows版での変更点と同じです。
【SimpleClient.cpp】 #include “stdThread.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 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]; m_pCMySyncObject = new CMySyncObject(); // ★ m_pCMySyncObject->Initialize(); // ★ // 起動パラメータチェック if (argc != 3) { fprintf(stderr, “Usage: %s <ServerAddress> <ServerPort>\n”, argv[0]); 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))) { case 0: // 入力なし、何もしない break; case CMD_QUIT_CHAR: // ‘q’入力終了 goto L_END; case CMD_SEND_MSG_CHAR: // ‘m’入力szKeyInBuffに文字列が格納されている // ★送信は送信データをセットするだけ、実際の送信はCSendRecvThreadで実施 m_pCSendRecvThread->SetSendData(szKeyInBuff, strlen(szKeyInBuff)); // メニューの表示 DispMenu(); break; } } L_END: // 切断とすべてのソケットの破棄 Stop(); m_pCMySyncObject->Uninitialize(); // ★ SAFE_DELETE(m_pCMySyncObject) // ★ return(0); } //———————————————- // 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) { 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) { if (fd != INVALID_SOCKET) { fprintf(stderr, “DestroySocket()\n”); shutdown(fd, SHUT_RDWR); // 受信も送信も停止 close(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, “\x1b[1;1H\x1b[0J”); fprintf(stderr, “%c:quit %c:メッセージ送信 : “, CMD_QUIT_CHAR, CMD_SEND_MSG_CHAR); } #if 0 //———————————————- // function // キーボード入力(改行まで)の取得 // 入力がないときはすぐにリターン // parameter // LPSTR pszString [in/out]データ格納用バッファ // int iSize [in]バッファのサイズ // return // 0:入力なし CMD_QUIT_CHAR:終了 CMD_SEND_MSG_CHAR:メッセージ送信 //———————————————- int GetKeyString(LPSTR pszString, int iSize) { BOOL fRet = FALSE; int iRet = 0; if (kbhit()) { switch (getchar()) { case CMD_QUIT_CHAR: iRet = CMD_QUIT_CHAR; break; case CMD_SEND_MSG_CHAR: fprintf(stderr, “\nInput Msg : “); 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); } #else // リモートデバッグではttyじゃないのでkbhitは使えない // 標準入力は0なのでselectのままでOKですが、練習のためにpollに int GetKeyString(LPSTR pszString, int iSize) { BOOL fRet = FALSE; int iRet = 0; pollfd fds[1] = {0}; fds[0].fd = 0; // 0:標準入力 1:標準出力 2:標準エラー fds[0].events = POLLIN; // Enterキー入力を対象に poll(fds, 1, 10); if (fds[0].revents & POLLIN) // ENTERキー入力があった { memset(pszString, 0, iSize); read(0, pszString, iSize); switch (pszString[0]) { case CMD_QUIT_CHAR: iRet = CMD_QUIT_CHAR; break; case CMD_SEND_MSG_CHAR: while (1) { poll(fds, 1, 10); if (fds[0].revents & POLLIN) read(0, pszString, iSize); else break; } fprintf(stderr, “\n送信メッセージを入力してください : “); memset(pszString, 0, iSize); read(0, pszString, iSize); // ENTERキーまで読み込む 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); } #endif //———————————————- // 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); }
これで完成です。
次回は、ここまでのまとめとして、サーバ側に送信機能、クライアント側に受信機能を
追加してみることにしましょう。