ネットワークプログラミング(後編)では、クライアントからサーバへの
ファイル送信機能を実装しました。
前々回、前回に作成したクライアント(送信機能)サーバ(受信機能)
参照してください。
動作は下図のようになっています。

赤字部分は未実装です。
データの流れは、クライアントからサーバへの一方向です。
ネットワークを使ったアプリケーションではクライアント・サーバ間で双方に
コミュニケーションを図る必要がある場面があります。
今回は、TCP/IPプログラムのまとめとして、赤字部分を実装し、双方向通信を実現する
方法を解説します。

送信については、CSendRecvThreadの送信リングバッファにデータを書き込めば
実現できるので、サーバ側に応答送信を組み込むのは簡単です。
受信の方が難しいです。
 送信は送信側が希望するタイミングで送信を行うことができます。
 受信は、受信側でいつデータが来るのかを知ることはできません。

すでにサーバ側ではファイル受信処理を実現しているので、これを真似ればクライアントに
要求応答の受信を組み込むことができそうです。
サーバでは、CSendRecvThreadがファイル送信コマンドをCRecvFileThreadの
コマンド格納用リングバッファに格納し、それをCRecvFileThreadで次のように
ファイル送信コマンドが格納されたバッファを常時調べるようにして、ファイル送信
コマンドに対する処理を行っています。

【RecvFileThread.cpp】 UINT CRecvFileThread::DoWork() { BOOL fRet = TRUE; // ★このループでAnalyzeCmd()が常に実行される while (!m_fStopFlag) { usleep(10 * 1000); // ★リングバッファに格納されているファイル送信コマンドを調べ // 各コマンドに対する処理を実行する if (AnalyzeCmd() == -1) { fRet = FALSE; break; } } m_pCMySyncObject->Lock(); m_fIamZombie = TRUE; m_pCMySyncObject->UnLock(); return((fRet == TRUE) ? 0 : -1); }

この構造をクライアント(CSendRecvThread、CSendFileThread)に組み込むことにします。

それでは、サーバにファイル送信開始要求に対する応答を追加しましょう。
元にするプロジェクトは、SimpleServerTransFile for Linuxです。
CRecvFileThreadのRecvSendFileStartPacket()に応答送信を追加します。
上図の「②応答待ち★←結果を送信する」の部分です。
RecvFileThraed.hにファイル送信要求に対する応答を送信する関数の宣言を追加します。

【RecvFileThread.h】 #include “ThreadJob.h” #include “define.h” // パケット作成のため … class CRecvFileThread : public CThreadJob { … private: … int RecvSendFileEndPacket(SendFileEndReqRec *pbPacket); // ③ファイルをクローズする BOOL SendSendFileStartResPacket(char cResCode); // ★ファイル送信開始要求に対する応答を送信する };

実際にファイル送信要求に対する応答を送信する関数です。
単純にパケットを作成して、CSendRecvThreadの送信リングバッファに書き込むだけです。

【RecvFileThread.cpp】 //============================================== // function // ★ファイル送信開始要求に対する応答を送信する // parameter // char cResCode [in]応答コード 1/-1 // return // TRUE/FALSE //============================================== BOOL CRecvFileThread::SendSendFileStartResPacket(char cResCode) { BOOL fRet = FALSE; SendFileResRec *pSendFileStartRes = NULL; int iPacketSize; iPacketSize = sizeof(SendFileResRec); pSendFileStartRes = (SendFileResRec *)calloc(iPacketSize, sizeof(BYTE)); memcpy(pSendFileStartRes->header.bMagicData, MAGIC_STRING, strlen(MAGIC_STRING)); pSendFileStartRes->header.wCommand = htons(CMD_SEND_FILE_START_RES); pSendFileStartRes->header.wDataLen = htons(sizeof(char)); pSendFileStartRes->cResCode = cResCode; fRet = m_pCSendRecvThread->SetSendData((char *)pSendFileStartRes, iPacketSize); return(fRet); }

ファイル送信開始コマンド受信処理で、この関数を呼び出せば完成です。
クライアントでは開始要求応答を受信後、ファイル送信中コマンドを送り始めるので
ここで、念のためファイル送信コマンド受信用リングバッファを空にします。

【RecvFileThread.cpp】 //============================================== // function // ①ファイル送信開始コマンドの受信 // parameter // SendFileStartReqRec *pbPacket [in]ファイル送信開始コマンドパケット // return // 1:解析成功 -1:致命的エラー //============================================== int CRecvFileThread::RecvSendFileStartPacket(SendFileStartReqRec *pbPacket) { int iRet = 0; Locate(1, 11, 2); fprintf(stderr, “Start %s %s(%u)”, pbPacket->FileInfo.szFileName, pbPacket->FileInfo.szFileSize, timeGetTime()); if (m_iRecvFileStatus != RECVC_FILE_STAT_IDLE) { iRet = -1; // 遷移状態エラーは切断する goto L_END; } // ここで書き込み用にファイルをopenする if ((m_fdDstFile = open(pbPacket->FileInfo.szFileName, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) == -1) { SendSendFileStartResPacket(-1); // ★エラー発生を送信 goto L_END; } // 開始したファイル名を覚える DISABLE_C4996 strcpy(m_szFileName, pbPacket->FileInfo.szFileName); ENABLE_C4996 m_iRecvFileStatus = RECVC_FILE_STAT_ING; m_pCRingBuffCmd->Clear(); // ★ファイル送信コマンドバッファを空にする SendSendFileStartResPacket(1); // ★準備OKを送信 iRet = 1; L_END: if (iRet == -1) // エラー時オープンしているファイルがあればクローズ、削除 { if (m_fdDstFile != -1) { close(m_fdDstFile); unlink(m_szFileName); } m_fdDstFile = -1; } return(iRet); }

次に、クライアントをファイル送信開始要求応答を待ってからデータを送信し始めるように
変更
します。
元にするプロジェクトは、SimpleClientTransFile for Windowsです。
前述したようにCSendFileThreadでファイル送信コマンド/応答格納リングバッファを
常時調べるようします。
CSendFileThreadのDoWork()は次のようにします。

【SendFileThraed.cpp】 UINT CSendFileThread::DoWork() { int iRet = -1; while (!m_fStopFlag) { Sleep(5); // Sleepを入れて他のスレッドにチャンスを与える // ★ファイル送信遷移状態に応じた処理の実行 // 応答に対する処理とファイル送信処理をこの関数で実行します iRet = DoSendFileState(); if ((iRet == 2) || (iRet == -1)) // 送信完了/エラー、このスレッドをゾンビにする break; } // ファイルのclose if (m_fdSrcFile != -1) _close(m_fdSrcFile); m_pCMySyncObject->Lock(); m_fIamZombie = TRUE; // ファイル送信が完了/失敗でゾンビ m_pCMySyncObject->UnLock(); return((iRet != -1) ? 0 : -1); }

繰り返し実行されるDoSendFileState()でファイル送信処理とファイル送信コマンド/応答用
リングバッファに格納されたデイタを処理するために
ファイル送信の遷移状態を表す定数管理する変数を定義します。
ファイル送信コマンド/応答用リングバッファ解析・応答を実行する関数を定義します。
ファイルディスクリプタなどファイルアクセスに必要な変数をメンバ変数に変更します。

【SendFileThraed.h】 #pragma once #include “ThreadJob.h” #include “define.h” // パケット作成のため #define CMD_BUFF_SIZE (1024 * 64) // ★ファイル送信コマンド/応答格納リングバッファのサイズ #define TIMEOUT_FOR_RES (1000) // ★応答までのタイムアウト時間msec // ★ファイル送信遷移状態 #define SEND_FILE_STAT_IDLE 0 // 初期状態 #define SEND_FILE_STAT_START 1 // ファイル送信開始要求応答待ち #define SEND_FILE_STAT_ING 2 // ファイル送信中 #define SEND_FILE_STAT_END 3 // ファイル送信完了要求応答待ち class CMySyncObject; // CMySyncObjectを使うため class CSendRecvThread; // CSendRecvThreadを使うため class CRingBuff; // ★CRingBuffを使うため class CSendFileThread : public CThreadJob { public: CSendFileThread(CSendRecvThread *pCSendRecvThread, LPSTR pszFileName); ~CSendFileThread(); CRingBuff *m_pCRingBuffCmd; // ★ファイル送信コマンド/応答格納リングバッファ UINT DoWork() override; BOOL IsZombie(); // このスレッドはゾンビ状態か private: LPSTR m_pszFileName; // 送信ファイル名 CSendRecvThread *m_pCSendRecvThread; // 送受信スレッド(親スレッド) CMySyncObject *m_pCMySyncObject; BOOL m_fIamZombie; // ゾンビ状態か(送信終了) int m_iSendFileStatus; // ★遷移状態 SEND_FILE_STAT_IDLE/SEND_FILE_STAT_START/SEND_FILE_STAT_ING/SEND_FILE_STAT_END char m_cSendFileStartRes; // ★ファイル送信開始要求応答(0:初期値 1:OK -1:エラー) DWORD m_dwStart; // ★応答待ちタイムアウトのため要求を行った時刻 int m_fdSrcFile; // ★送信ファイルディスクリプタ long m_lFileLength; // ★送信ファイルの長さ long m_lTotalSentSize; // ★送信済みバイト数 int m_iWaitCount; // ★送信待ちをした回数 int DoSendFileState(); // ★遷移状態に応じたファイル送信処理,応答処理を実行 // ファイル送信処理 int SendFileStart(); // ★①ファイル送信開始処理 int CheckSendFileStartRes(DWORD dwTimeOut); // ★②ファイル送信開始要求応答チェック BOOL SendSendFileStartPacket(LPSTR pszFileName, int iFileLength); // ①ファイル送信開始要求送信 int SendFileIng(); // ★③④ファイル途中処理、ファイル送信完了処理 // 応答処理 int AnalyzeCmd(); // ★ファイル送信コマンド/応答パケットの解析 int RecvSendFileStartResPacket(SendFileResRec *pbPacket); // ★ファイル送信開始要求応答処理 int RecvSendFileEndResPacket(SendFileResRec *pbPacket); // ★ファイル送信完了要求応答処理 int RecvSendFileAbortResPacket(SendFileResRec *pbPacket); // ★ファイル送信中断要求応答処理 };

それでは、SendFileThread.cppを順に見てみましょう。
追加したメンバ変数の初期化をコンストラクタに追加します。
デストラクタに開放を追加します。

【SendFileThread.cpp】 #include “SendFileThread.h” #include “MySyncObject.h” // CMySyncObjectを使うため #include “SendRecvThread.h” // CSendRecvThreadを使うため #include “RingBuff.h” // ★CRingBuffを使うため CSendFileThread::CSendFileThread(CSendRecvThread *pCSendRecvThread, LPSTR pszFileName) { // コンストラクタの引数を覚える m_pCSendRecvThread = pCSendRecvThread; m_pszFileName = (LPSTR)calloc(strlen(pszFileName) + 1, sizeof(char)); DISABLE_C4996 strcpy(m_pszFileName, pszFileName); ENABLE_C4996 // 変数の初期化 m_fIamZombie = FALSE; m_pCMySyncObject = new CMySyncObject(); m_pCMySyncObject->Initialize(); m_pCRingBuffCmd = new CRingBuff(CMD_BUFF_SIZE); // ★ファイル送信要求/応答格納用 m_iSendFileStatus = SEND_FILE_STAT_IDLE; // ★遷移状態 SEND_FILE_STAT_IDLE/SEND_FILE_STAT_START/SEND_FILE_STAT_ING/SEND_FILE_STAT_END m_cSendFileStartRes = 0; // ★ファイル送信開始要求応答(0:初期値 1:OK -1:エラー) m_dwStart = 0; // ★要求を送信した時刻 m_fdSrcFile = -1; // ★送信ファイルディスクリプタ m_lTotalSentSize = 0; // ★送信済みバイト数 m_iWaitCount = 0; // ★送信待ちをした回数 } CSendFileThread::~CSendFileThread() { if(m_pCMySyncObject != NULL) m_pCMySyncObject->Uninitialize(); SAFE_DELETE(m_pCMySyncObject) SAFE_FREE(m_pszFileName) SAFE_DELETE(m_pCRingBuffCmd) // ★ファイル送信要求/応答格納用 }

DoWork()は前述の通りで、ファイル送信を遷移状態に応じて処理、応答に対する処理する
関数DoSendFileState()を呼ぶようにします。
ファイル送信が完了またはエラーになれば、このスレッドをゾンビにして終了するのを
待ちます。
DoSendFileState()は、応答の解析、遷移状態に応じた処理を行う関数を呼び出します。
それぞれの関数の返り値に応じて遷移状態を変更します。

UINT CSendFileThread::DoWork() { int iRet = -1; while (!m_fStopFlag) { Sleep(5); // Sleepを入れて他のスレッドにチャンスを与える // ★ファイル送信遷移状態に応じた処理の実行 // 応答に対する処理とファイル送信処理をこの関数で実行します iRet = DoSendFileState(); if ((iRet == 2) || (iRet == -1)) // 送信完了/エラー、このスレッドをゾンビにする break; } // ファイルのclose if (m_fdSrcFile != -1) _close(m_fdSrcFile); m_pCMySyncObject->Lock(); m_fIamZombie = TRUE; // ファイル送信が完了/失敗でゾンビ m_pCMySyncObject->UnLock(); return((iRet != -1) ? 0 : -1); } //============================================== // function // ★ファイル送信遷移状態に応じた処理を実行 // parameter // なし // return // 0:何もしなかった 1:処理を実行した 2:送信完了 -1:エラー発生 //============================================== int CSendFileThread::DoSendFileState() { int iRet = 0; if (AnalyzeCmd() == -1) // 応答などの解析 返値が-1の時がエラー { iRet = -1; goto L_END; } switch (m_iSendFileStatus) { case SEND_FILE_STAT_IDLE: // 初期状態->送信開始 if ((iRet = SendFileStart()) == 1) // ①ファイルをOpenして、送信開始要求パケットを送信する { m_iSendFileStatus = SEND_FILE_STAT_START; m_dwStart = timeGetTime(); } break; case SEND_FILE_STAT_START: // 送信開始->送信中 // ★②応答結果チェック if ((iRet = CheckSendFileStartRes(TIMEOUT_FOR_RES)) == 1) { m_iSendFileStatus = SEND_FILE_STAT_ING; m_lTotalSentSize = 0; // ファイル受信準備 m_iWaitCount = 0; } break; case SEND_FILE_STAT_ING: // 送信中->送信中/送信中->送信完了 if ((iRet = SendFileIng()) == 2) // 送信中->送信完了 { m_iSendFileStatus = SEND_FILE_STAT_IDLE; iRet = 2; } break; default: // 知らない遷移状態 iRet = -1; break; } L_END: return(iRet); }

SendFileStart()はファイル操作関係の変数をメンバ変数に置き換えます。
 m_fdSrcFile、m_lFileLength
 開始要求応答を待つためにm_cSendFileStartResを初期化します。
SendSendFileStartPacket()は変更なしです。

//============================================== // function // ★①ファイルをOpenして、送信開始要求パケットを送信する // parameter // なし // retun // 1/-1 //============================================== int CSendFileThread::SendFileStart() { int iRet = -1; struct stat statInfo; // ファイルの情報のための変数 m_cSendFileStartRes = 0; // 応答受信準備 // ファイルのOpen DISABLE_C4996 m_fdSrcFile = _open(m_pszFileName, _O_RDONLY | _O_BINARY); ENABLE_C4996 if (m_fdSrcFile == -1) goto L_END; // ファイルの情報を取得(ファイルサイズ) if (fstat(m_fdSrcFile, &statInfo) != 0) goto L_END; m_lFileLength = statInfo.st_size; // ファイル名, ファイル長の送信 if (SendSendFileStartPacket(m_pszFileName, m_lFileLength) == FALSE) goto L_END; iRet = 1; L_END: return(iRet); }

CheckSendFileStartRes()はファイル送信開始要求に対する応答を調べます。
m_cSendFileStartResの更新はAnalyzeCmd()で実施されます。
応答が返ってこない時タイムアウトエラーにします。

//============================================== // function // ★②ファイル送信開始要求応答のチェック // parameter // DWORD dwTimeOut [in]タイムアウト // return // 0:まだ来ていない 1:成功が来た -1:失敗またはタイムアウト //============================================== int CSendFileThread::CheckSendFileStartRes(DWORD dwTimeOut) { int iRet = 0; if (GetdwInterval(timeGetTime(), m_dwStart) >= dwTimeOut) // タイムアウト { iRet = -1; goto L_END; } if (m_cSendFileStartRes == -1) // エラー応答 iRet = -1; else if (m_cSendFileStartRes == 1) // 成功応答 iRet = 1; L_END: return(iRet); }

SendFileIng()はファイル操作関係の変数をメンバ変数に置き換えます。

#define READ_SIZE (1024 * 10) // ファイルの読み込み単位(ガバッと読みます) //============================================== // function // ★③④ファイルを読み込んでファイル送信中/ファイル送信完了パケットを送信する // この関数でエラーが起こったときは、中断コマンドを送るのも良いでしょう // parameter // なし // return // TRUE/FALSE //============================================== int CSendFileThread::SendFileIng() { BOOL fEnd = FALSE, fRet; LPBYTE pbData = NULL; int iRet = 0, iReadSize; SendFileIngRec *pSendFileIng = NULL; int iPacketSize; // ファイルを読み込む pbData = (LPBYTE)calloc(READ_SIZE, sizeof(BYTE)); _lseek(m_fdSrcFile, m_lTotalSentSize, SEEK_SET); iReadSize = _read(m_fdSrcFile, pbData, READ_SIZE); iPacketSize = sizeof(HeaderRec) + sizeof(SendFileStatRec) + iReadSize; pSendFileIng = (SendFileIngRec *)calloc(iPacketSize, sizeof(BYTE)); // 共通ヘッダ部にデータを格納する memcpy(pSendFileIng->header.bMagicData, MAGIC_STRING, strlen(MAGIC_STRING)); pSendFileIng->header.wDataLen = htons((WORD)sizeof(SendFileStatRec) + iReadSize); // データ部にデータを格納する DISABLE_C4996 sprintf(pSendFileIng->Stat.szOffset, “%d”, m_lTotalSentSize); // 読み込み位置 sprintf(pSendFileIng->Stat.szSize, “%d”, iReadSize); // 読み込んだデータ数 ENABLE_C4996 if (iReadSize > 0) // ファイルの途中を送信 { Locate(1, 8, 1); fprintf(stderr, “ファイル送信中 %d %d %d\n”, m_lTotalSentSize, iReadSize, m_lFileLength); // 共通ヘッダ部にデータを格納する pSendFileIng->header.wCommand = htons(CMD_SEND_FILE_ING); // データ部にデータを格納する memcpy(pSendFileIng->bData, pbData, iReadSize); } else // ファイルの最後をまで読んだので完了を送信 { Locate(1, 9, 1); fprintf(stderr, “ファイル送信完了 %d %d\n”, m_lTotalSentSize, iReadSize); // 共通ヘッダ部にデータを格納する pSendFileIng->header.wCommand = htons(CMD_SEND_FILE_END_REQ); fEnd = TRUE; // ファイル送信完了 } // パケット全体を送信バッファに書きこむ fRet = m_pCSendRecvThread->SetSendData((char *)pSendFileIng, iPacketSize); if (fRet == TRUE) // パケットを書き込めた { if (fEnd == TRUE) // 最後まで送信したので終わり iRet = 2; else { m_lTotalSentSize += iReadSize; // パケットを書き込めたので読み込みポインタ更新 iRet = 1; } } else // 書き込めなかったので読み込みポインタはそのまま { Locate(1, 10, 0); fprintf(stderr, “送信バッファの空き待ち(%d)\n”, ++m_iWaitCount); // 送信バッファに書けなかったのでちょっと待つ Sleep(100); // READ_SIZEを送信するのに掛かる時間程度待つのが良いでしょう } SAFE_FREE(pSendFileIng) SAFE_FREE(pbData) return(iRet); }

AnalyzeCmd()ではファイル送信コマンド要求/応答が格納されたリングバッファ
m_pCRingBuffCmdを調べて、処理を行います。
コマンドに応じてRecvSendFileStartResPacket()、RecvSendFileEndResPacket()、
RecvSendFileAbortResPacket()を呼び出します。

//============================================== // function // ★ファイル送信コマンド/応答の解析 // parameter // なし // return // 0:十分なデータが格納されていないなど 1:解析成功 -1:解析失敗 //============================================== int CSendFileThread::AnalyzeCmd() { int iRet = 0; HeaderRec Header; WORD wCmd; int iSize = m_pCRingBuffCmd->GetReadableSize(); int iPacketSize, iDataSize; LPBYTE pbPacket = NULL; if (iSize < sizeof(HeaderRec)) // ヘッダサイズに満たないときは何もしない goto L_END; // ヘッダ部を借り読み込みする(パケット丸ごと格納されているはず) m_pCRingBuffCmd->ReadWithoutUpdateHeadPoint((LPBYTE)&Header, sizeof(HeaderRec)); wCmd = ntohs(Header.wCommand); iDataSize = ntohs(Header.wDataLen); iPacketSize = iDataSize + sizeof(HeaderRec); if (iSize < iPacketSize) // パケットサイズに満たないときは何もしない goto L_END; pbPacket = (LPBYTE)calloc(iPacketSize, sizeof(BYTE)); // パケット全体を読む m_pCRingBuffCmd->Read(pbPacket, iPacketSize); switch (wCmd) { case CMD_SEND_FILE_START_RES: // 送信開始要求応答受信 iRet = RecvSendFileStartResPacket((SendFileResRec *)pbPacket); break; case CMD_SEND_FILE_END_RES: // 送信完了要求応答受信 iRet = RecvSendFileEndResPacket((SendFileResRec *)pbPacket); break; case CMD_SEND_FILE_ABORT_RES: // 送信中断要求応答受信 iRet = RecvSendFileAbortResPacket((SendFileResRec *)pbPacket); break; default: iRet = -1; break; } L_END: return(iRet); }

RecvSendFileStartResPacket()、RecvSendFileEndResPacket()、
RecvSendFileAbortResPacket()は受信した応答に対する処理です。
ここでは、RecvSendFileStartResPacket()のみ実装してあります。
 m_cSendFileStartResの更新

//============================================== // function // ★ファイル送信開始要求応答処理 // parameter // なし // return // TRUE/FALSE //============================================== int CSendFileThread::RecvSendFileStartResPacket(SendFileResRec *pbPacket) { int iRet = 1; m_cSendFileStartRes = pbPacket->cResCode; return(iRet); } //============================================== // function // ★ファイル送信完了要求応答処理(未実装) // parameter // なし // return // TRUE/FALSE //============================================== int CSendFileThread::RecvSendFileEndResPacket(SendFileResRec *pbPacket) { int iRet = 0; return(iRet); } //============================================== // function // ★ファイル送信中断要求応答処理(未実装) // parameter // なし // return // TRUE/FALSE //============================================== int CSendFileThread::RecvSendFileAbortResPacket(SendFileResRec *pbPacket) { int iRet = 0; return(iRet); }

IsZombie()は変更なしです。
これでCSendFileThreadの更新は完了です。
次に、CSendRecvThreadで応答を受信したときにCSendFileThreadに渡す部分を作れば
完成です。

SendRecvThread.hに応答を受信したときにCSendFileThreadに渡す関数の宣言を追加します。

【SendRecvThread.h】 #pragma once … class CSendRecvThread : public CThreadJob { public: … private: … int RecvMessagePacket(HeaderRec *pHeader); // 引数と返値を変更 int RecvFileSendResPacket(HeaderRec *pHeader); // ★ファイル送信コマンド/応答の受信処理 … };

SendRecvThread.cppの変更箇所は2つです。
AnalyzeDataRecv()の変更
 応答を受信したときの処理を追加します。
RecvFileSendResPacket()の追加
 CSendFileThreadに渡す関数です。
 CSendFileThreadのm_pCRingBuffCmdに応答パケットを書き込むだけです。

【SendRecvThread.cpp】 //============================================== // function // 受信データの解析 // 受信リングバッファに格納されているデータを調べる // parameter // なし // return // 0:パケットが揃っていない // 1:パケットを受信したので処理を行った // -1:エラーが発生した //============================================== int CSendRecvThread::AnalyzeDataRecv() { int iRet = 0; HeaderRec Header; WORD wCmd; // データサイズを調べる int iSize = m_pCRingBuffRecv->GetReadableSize(); if (iSize < sizeof(HeaderRec)) // ヘッダサイズに満たないときは何もしない goto L_END; // ヘッダ部を借り読み込みする m_pCRingBuffRecv->ReadWithoutUpdateHeadPoint((LPBYTE)&Header, sizeof(HeaderRec)); // ヘッダ部の解析 if (memcmp(Header.bMagicData, MAGIC_STRING, strlen(MAGIC_STRING)) != 0) { iRet = -1; // 識別子が違うのでエラー goto L_END; } wCmd = ntohs(Header.wCommand); fprintf(stderr, “CMD:%d\n”, wCmd); switch (wCmd) { case CMD_MSG_DATA: iRet = RecvMessagePacket(&Header); break; case CMD_SEND_FILE_START_RES: // ★ファイル送信コマンド/応答の処理 case CMD_SEND_FILE_END_RES: case CMD_SEND_FILE_ABORT_RES: iRet = RecvFileSendResPacket(&Header); break; default: // 知らないコマンドなのでエラー iRet = -1; break; } L_END: return(iRet); } //============================================== // function // ★ファイル送信コマンド/応答(S→C)をファイル送信スレッドに渡す // parameter // HeaderRec *pHeader [in]仮読みしたヘッダ // return // 0:パケットが揃っていない, ファイル受信側が満杯 // 1:パケットを受信したので処理を行った // -1:エラーが発生した //============================================== int CSendRecvThread::RecvFileSendResPacket(HeaderRec *pHeader) { int iRet = 0; int iDataSize, iSize, iPacketSize; LPBYTE pbPacket = NULL; iDataSize = ntohs(pHeader->wDataLen); iPacketSize = iDataSize + sizeof(HeaderRec); iSize = m_pCRingBuffRecv->GetReadableSize(); if (iSize < iPacketSize) goto L_END; pbPacket = (LPBYTE)calloc(iPacketSize, sizeof(BYTE)); // パケット全体を仮読み m_pCRingBuffRecv->ReadWithoutUpdateHeadPoint(pbPacket, iPacketSize); // ファイル送信スレッドに渡せたら読み込んだことにする if (m_pCSendFileThread != NULL) { if (m_pCSendFileThread->m_pCRingBuffCmd->Write(pbPacket, iPacketSize) == TRUE) { m_pCRingBuffRecv->UpdateHeadPoint(iPacketSize); iRet = 1; } } else m_pCRingBuffRecv->UpdateHeadPoint(iPacketSize); // ファイル送信スレッドがないときは読み込んだことにする SAFE_FREE(pbPacket); L_END: return(iRet); }

これで完成です。
SimpleClientTransFile、SimpleServerTransFileに双方向通信を行うことができる
クライアントからのファイル送信機能が実装できました。
機能を指定したスレッドを追加することで、クライアント、サーバ間で機能する
アプリケーションのプロトタイプとして使えると思います。
様々な機能を追加してみてください。
以下はプロジェクトのリンクです。
これらのプロジェクトでは、ファイル送信完了応答、ファイル送信中断要求応答も
実装してあります。またチャットメッセージの機能も別スレッドとして
作り直してあります。参考にしてください。
クライアント
 SimpleClientTransFile for Windows
 SimpleClientTransFile for Linux
サーバ
 SimpleServerTransFile for Windows
 SimpleServerTransFile for Linux

これでIPv6/v4、Windows/Linuxで動作するTCP/IPのプログラムは終了です。
次回から、UDPのプログラムを始めることにしましょう。