プログラム構成について
ファイル送受信機能について次のような構成でプログラムを考えてみることにしましょう。

単純なファイル送受信機能ということでこのような仕様で考えます。
開始確認(受信側→送信側)、終了確認(受信側→送信側)、送信の中断(送信側→受信側)、
については後日実装することにします。
同名ファイルの同時送受信については考慮しないことにします。

CSendFileThreadやCRecvFileThreadは対応するCSendRecvThreadが有効な時(接続状態)
のみに機能するようにしなければなりません。
無効状態の時に送信バッファにデータを書こうとするとプログラムはハングするかも
しれません。
したがってCSendFileThreadやCRecvFileThreadの動作については対応する
CSendRecvThreadで管理するのが良さそうです。

ファイル送受信のためのパケットの仕様を決めましょう。
ClientからServerへ送るコマンド(要求)として
 送信開始要求、ファイル送信中、送信完了要求、中断要求
ServerからClientへ送るコマンド(応答)として
 送信開始要求に対する応答、送信完了要求に対する応答、中断要求に対する応答
ServerからClientへ送るコマンド(要求)として
 中断要求
あたりでどうでしょう。
ファイル送信中コマンドについては、いちいち応答をしていると速度もでないので
ClientからServerへ送りっぱなしということにします。
もしServerで問題が起こったら中断要求を送信することにします。

それぞれのコマンドで送信するデータは、次のようなものにしてみましょう。
 送信開始要求:共通ヘッダ、ファイル名、ファイルサイズ
 ファイル送信中:共通ヘッダ、オフセット、データサイズ、データ
 送信完了要求:共通ヘッダ、オフセット、データサイズ、データ
 中断要求:共通ヘッダ
 応答:共通ヘッダ、ステータス(応答内容コード)
これらの具体的な定義、構造体をdefine.hに組み込んだものが次のファイルです。

【define.h】 #pragma once #include “stdThread.h” #define MAGIC_DATA_LEN 36 #define MAGIC_STRING “E2E6F7BF-42B1-4382-AF0B-1F452ED13EB6” #define FILE_NAME_LEN 256 // ★ファイル名の長さ #define FILE_SIZE_DIGIT 10 // ★ファイルサイズの桁数 // Client->Server, Server->Client #define CMD_MSG_DATA 100 // Client->Server #define CMD_SEND_FILE_START_REQ 200 // ★送信開始要求 #define CMD_SEND_FILE_ING 201 // ★ファイル送信中 #define CMD_SEND_FILE_END_REQ 202 // ★送信完了要求 #define CMD_SEND_FILE_ABORT_REQ 203 // ★中断要求 // Server->Client #define CMD_SEND_FILE_START_RES 1200 // ★送信開始要求に対する応答 #define CMD_SEND_FILE_END_RES 1202 // ★送信完了要求に対する応答 #define CMD_SEND_FILE_ABORT_RES 1203 // ★中断要求に対する応答 #pragma pack(push ,1) //==================================== // 共通ヘッダ部 typedef struct { BYTE bMagicData[MAGIC_DATA_LEN]; // 不正アクセス防止用(WindowsのGUIDを使います) WORD wCommand; // ネットワークオーダー WORD wDataLen; // ネットワークオーダー } HeaderRec; //==================================== // メッセージ送信用構造体 共通ヘッダ部 + データ部 typedef struct { HeaderRec header; BYTE bMsgData[1]; // nullターミネイトを含まないデータ } MsgDataRec; //==================================== // ★データ部 ファイル送信開始要求 typedef struct { char szFileSize[FILE_SIZE_DIGIT + 1]; // ファイル長文字列(nullターミネートを含む) char szFileName[FILE_NAME_LEN + 1]; // ファイル名文字列(nullターミネートを含む)UTF-8 } FileInfoRec; typedef struct { HeaderRec header; FileInfoRec FileInfo; } SendFileStartReqRec; //==================================== // ★データ部 ファイル送信(ファイルの途中、最後) typedef struct { char szOffset[FILE_SIZE_DIGIT + 1]; // 先頭からの位置 char szSize[FILE_SIZE_DIGIT + 1]; // データのサイズ } SendFileStatRec; typedef struct { HeaderRec header; SendFileStatRec Stat; BYTE bData[1]; } SendFileIngRec, SendFileEndReqRec; //==================================== // ★データ部 ファイル送信中断要求 typedef struct { HeaderRec header; } SendFileAbortReqRec; //==================================== // ★データ部 ファイル送信要求に対する応答 typedef struct { HeaderRec header; char cResCode; // 1:成功 -1:失敗 など応答内容コード } SendFileResRec; #pragma pack(pop)

ファイル送信を実現するクラスCSendFileThreadの作成
次のような機能を考えます
CSendRecvThreadでファイル送信開始時にこのクラスを構築します。
構築時にファイル名と送受信スレッドを渡します。
動作は以下の通りです。
①指定されたファイルをオープンする
CMD_SEND_FILE_START_REQ 送信開始要求コマンド送信
②CMD_SEND_FILE_START_RES 応答を待ちます(*)
以下を繰り返します
 ③ファイルを読み込みます
  CMD_SEND_FILE_ING    ファイルデータ送信
ファイルの最後まで行ったら
④ファイルをクローズします
 CMD_SEND_FILE_END_REQ  送信完了コマンド送信
⑤CMD_SEND_FILE_END_RES  応答を待ちます(*)
 スレッドをゾンビ状態にする
CSendRecvThreadでゾンビ状態になったスレッドを破棄します
このスレッドが動いている間(ファイル送信中)に別のファイルの送信は
行わないことにします。
(*)②、⑤については後程実装することにして先ずは、これなしで作る
 ことにします

プロジェクト(SimpleClientTransFile)に次の変更を行います。
今回の仕様では、ファイルの置き場所をカレントフォルダにするので
デバッグ時も実行ファイルがある場所をカレントフォルダとするように
プロパティの変更を行います。
作業ディレクトリの変更
 $(ProjectDir)を$(OutDir)に変更します。

Linuxとの互換性を図るためにSimpleClient.cppに記述して以下の内容を
stdThread.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

ファイル操作のためのインクルードファイルもstdThread.hに追加します。
以下が変更後のstdThread.hです。

【stdThread.h】 #ifndef _stdThread_H_2B9D23FC_90BF_4898_B161_B894F01F6698 #define _stdThread_H_2B9D23FC_90BF_4898_B161_B894F01F6698 #include <winsock2.h> #include <stdio.h> #include <process.h> #include <iphlpapi.h> #include <Ws2tcpip.h> #include <io.h> // ★open,lseek, fstatのため #include <fcntl.h> // ★ #include <sys\types.h> // ★ #include <sys\stat.h> // ★ #pragma comment(lib, “iphlpapi.lib”) #pragma comment(lib, “Ws2_32.lib”) #pragma comment(lib, “Winmm.lib”) //======================================================================= #define SAFE_FREE(p) { if(p) { free(p); (p)=NULL; } } #define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } #define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p)=NULL; } } //======================================================================= // ★ #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 extern DWORD GetdwInterval(DWORD dwNow, DWORD dwPast); extern BOOL ConvUtf8toSJis(BYTE* pSource, BYTE* pDist, int* pSize); extern BOOL ConvSJistoUtf8(LPBYTE pbSource, LPBYTE pbDest, int *piSize); extern int DispErrorMsg(LPCSTR pszTitle); #endif

プロジェクトにCSendFileThread(SendFileThread.h, SendFileThread.cpp)を追加します。
 基底クラス:CThreadJob

SendFileThread.hは以下のようになります。
コンストラクタでは送受信スレッド(親スレッド)と送信ファイル名を渡します。
①ファイル送信開始要求送信、③④ファイル途中、ファイル送信完了要求送信を
実現するメソッドを定義しています。

【SendThread.h】 #pragma once #include “ThreadJob.h” #include “define.h” // パケット作成のため class CMySyncObject; // CMySyncObjectを使うため class CSendRecvThread; // CSendRecvThreadを使うため class CSendFileThread : public CThreadJob { public: CSendFileThread(CSendRecvThread *pCSendRecvThread, LPSTR pszFileName); ~CSendFileThread(); UINT DoWork() override; BOOL IsZombie(); // このスレッドはゾンビ状態か private: LPSTR m_pszFileName; // 送信ファイル名 CSendRecvThread *m_pCSendRecvThread; // 送受信スレッド(親スレッド) CMySyncObject *m_pCMySyncObject; BOOL m_fIamZombie; // ゾンビ状態か(送信終了) BOOL SendSendFileStartPacket(LPSTR pszFileName, int iFileLength); // ①ファイル送信開始要求送信 BOOL SendSendFilePacket(int fd, int iFileLength); // ③④ファイル途中、ファイル送信完了要求送信 };

SendFileThread.cppは以下のようになります。
Windowsではファイルのオープンでは次のように
 fdSrcFile = _open(m_pszFileName, _O_RDONLY | _O_BINARY);
_O_BINARYを指定します。これを指定しまいとLFがCR+LFに変わってしまいます。
②⑤の応答待ちは、ここでは実装していません。

【SendThread.cpp】 #include “SendFileThread.h” #include “MySyncObject.h” // CMySyncObjectを使うため #include “SendRecvThread.h” // CSendRecvThreadを使うため 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(); } CSendFileThread::~CSendFileThread() { if(m_pCMySyncObject != NULL) m_pCMySyncObject->Uninitialize(); SAFE_DELETE(m_pCMySyncObject) SAFE_FREE(m_pszFileName) } UINT CSendFileThread::DoWork() { int fdSrcFile; // 送信ファイルディスクリプタ struct stat statInfo; // ファイルの情報のための変数 long lFileLength; //==================================================== // ①ファイルをOpenして、送信開始要求パケットを送信する // ファイルのOpen DISABLE_C4996 fdSrcFile = _open(m_pszFileName, _O_RDONLY | _O_BINARY); ENABLE_C4996 if (fdSrcFile == -1) goto L_END; // ファイルの情報を取得(ファイルサイズ) if (fstat(fdSrcFile, &statInfo) != 0) goto L_END; lFileLength = statInfo.st_size; // ファイル名, ファイル長の送信 if (SendSendFileStartPacket(m_pszFileName, lFileLength) == FALSE) goto L_END; //==================================================== // ★②ここで応答待ちをするようにする //==================================================== // ③④ファイルを読み込んでファイル送信中/ファイル送信完了パケットを送信する // ★この関数でエラーが起こったときは、中断コマンドを送るようにするのが良いでしょう if (SendSendFilePacket(fdSrcFile, lFileLength) == FALSE) goto L_END; //==================================================== // ★⑤ここで応答待ちをするようにする //==================================================== L_END: // ファイルのclose if (fdSrcFile != -1) _close(fdSrcFile); m_pCMySyncObject->Lock(); m_fIamZombie = TRUE; // ファイル送信が完了/失敗でゾンビ m_pCMySyncObject->UnLock(); return(0); // 成功/失敗で返値を変えるのもありでしょう } //============================================== // function // ①ファイル送信開始コマンドの送信 // parameter // LPSTR pszFileName [in]ファイル名 // int iFileLength [in]ファイル長 // retun // TRUE/FALSE //============================================== BOOL CSendFileThread::SendSendFileStartPacket(LPSTR pszFileName, int iFileLength) { BOOL fRet = FALSE; SendFileStartReqRec *pSendFileStartReq = NULL; int iPacketSize; int iSize; LPBYTE pbDest = NULL; // 共通ヘッダ部にデータを格納 iPacketSize = sizeof(SendFileStartReqRec); pSendFileStartReq = (SendFileStartReqRec *)calloc(iPacketSize, sizeof(BYTE)); memcpy(pSendFileStartReq->header.bMagicData, MAGIC_STRING, strlen(MAGIC_STRING)); pSendFileStartReq->header.wCommand = htons(CMD_SEND_FILE_START_REQ); pSendFileStartReq->header.wDataLen = htons(sizeof(FileInfoRec)); fprintf(stderr, “\x1b[8;1H\x1b[0K”); fprintf(stderr, “ファイル送信 %s %d”, pszFileName, iFileLength); // データ部にデータを格納(ファイルサイズ、ファイル名) // 漢字ファイル名対応のためにUTF-8に変更して送信する iSize = 0; pbDest = NULL; ConvSJistoUtf8((LPBYTE)pszFileName, NULL, &iSize); pbDest = (LPBYTE)calloc(iSize, sizeof(BYTE)); ConvSJistoUtf8((LPBYTE)pszFileName, pbDest, &iSize); memcpy(pSendFileStartReq->FileInfo.szFileName, pbDest, iSize); DISABLE_C4996 sprintf(pSendFileStartReq->FileInfo.szFileSize, “%d”, iFileLength); ENABLE_C4996 // ファイル送信開始要求パケットの送信 fRet = m_pCSendRecvThread->SetSendData((char *)pSendFileStartReq, iPacketSize); SAFE_FREE(pbDest) SAFE_FREE(pSendFileStartReq); return(fRet); } #define READ_SIZE (1024 * 10) // ファイルの読み込み単位(ガバッと読みます) //============================================== // function // ファイルを読み込みながらファイル送信コマンドの送信 // parameter // int fd [in]ファイルディスクリプタ // int iFileLength [in]ファイル長 // retun // TRUE/FALSE //============================================== BOOL CSendFileThread::SendSendFilePacket(int fd, int iFileLength) { BOOL fRet = FALSE, fEnd = FALSE; LPBYTE pbData = NULL; int iReadSize, iTotalSize = 0; SendFileIngRec *pSendFileIng = NULL; int iPacketSize; pbData = (LPBYTE)calloc(READ_SIZE, sizeof(BYTE)); while (!m_fStopFlag) { _lseek(fd, iTotalSize, SEEK_SET); iReadSize = _read(fd, 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”, iTotalSize); // 読み込み位置 sprintf(pSendFileIng->Stat.szSize, “%d”, iReadSize); // 読み込んだデータ数 ENABLE_C4996 if (iReadSize > 0) // ファイルの途中を送信 { fprintf(stderr, “\x1b[8;1H\x1b[0K”); fprintf(stderr, “ファイル送信中 %d %d %d”, iTotalSize, iReadSize, iFileLength); // 共通ヘッダ部にデータを格納する pSendFileIng->header.wCommand = htons(CMD_SEND_FILE_ING); // データ部にデータを格納する memcpy(pSendFileIng->bData, pbData, iReadSize); } else // ファイルの最後をまで読んだので完了を送信 { fprintf(stderr, “\x1b[9;1H\x1b[0K”); fprintf(stderr, “ファイル送信完了 %d %d\n”, iTotalSize, iReadSize); // 共通ヘッダ部にデータを格納する pSendFileIng->header.wCommand = htons(CMD_SEND_FILE_END_REQ); fEnd = TRUE; // ファイル送信完了 } // パケット全体を送信バッファに書きこむ fRet = m_pCSendRecvThread->SetSendData((char *)pSendFileIng, iPacketSize); SAFE_FREE(pSendFileIng) if (fRet == TRUE) // パケットを書き込めた { if (fEnd == TRUE) // 最後まで送信したので終わり break; iTotalSize += iReadSize; // パケットを書き込めたので読み込みポインタ更新 } else // 書き込めなかったので読み込みポインタはそのまま { fprintf(stderr, “\x1b[10;1H\x1b[0K”); fprintf(stderr, “送信バッファの空き待ち”); // 送信バッファに書けなかったのでちょっと待つ Sleep(100); // ★READ_SIZEを送信するのに掛かる時間程度待つのが良いでしょう } } SAFE_FREE(pbData) return(fRet); } //============================================== // function // このスレッドはゾンビ状態か // 別スレッドから参照される // parameter // なし // return // TRUE/FALSE //============================================== BOOL CSendFileThread::IsZombie() { BOOL fRet; m_pCMySyncObject->Lock(); fRet = m_fIamZombie; m_pCMySyncObject->UnLock(); return(fRet); }

CSendRecvThreadにCSendFileThreadの呼び出し(構築・破棄)を追加します。
SimpleClient.cpp(メインスレッド)から呼び出すためのファイル送信開始メソッドを
追加します。
先ずは、SendRecvThread.hです。変更点には★を付けました。

【SendRecvThread.h】 #pragma once #include “ThreadJob.h” #include “define.h” // RecvMessagePacketの引数のため class CMySyncObject; // このクラスの使用することを宣言 class CRingBuff; // CRingBuffを使用するため class CSendFileThread; // ★CSendFileThreadを使用するため #define SEND_BUFF_SIZE (1024 * 64) // 送信リングバッファのサイズ #if 1 #define SENDBUFSIZE (1024 + sizeof(HeaderRec)) // 一度に送信するサイズの最大値(共通ヘッダ分を加えてあります) #else #define SENDBUFSIZE (1024 * 4) // TCPで送信速度を1Mbpsより上げたいときは断片化処理をTCPのスタックに任せる // スタック(send)に渡すサイズを増やす #endif #define RECV_BUFF_SIZE (1024 * 64) // 受信リングバッファのサイズ #define RCVBUFSIZE (1024 * 2) // 一度に読む最大受信サイズ 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(); // このスレッドはゾンビ状態か BOOL SendFile(LPSTR pszFileName); // ★ファイル送信の実行 private: ConnectionInfoRec *m_pConInfo; // コンストラクタで渡されるパラメータを格納 // このスレッド実行中領域が確保されていること BOOL m_fIamZombie; // ゾンビ状態かどうかを保持 CRingBuff *m_pCRingBuffSend; // 送信データ格納用リングバッファ CRingBuff *m_pCRingBuffRecv; // 受信データ格納用リングバッファ CSendFileThread *m_pCSendFileThread; // ★ファイル送信スレッドは必要な時に作成し不要になれば破棄する DWORD m_dwPrevSentTime; // 前回送信した時刻(msec) DWORD m_dwSendInterval; // 次回送信までの間隔(msec) int GetSendData(char **ppcData); // 送信データの取得 int AnalyzeDataRecv(); // 引数と返値を変更 int RecvMessagePacket(HeaderRec *pHeader); // 引数と返値を変更 DWORD CalcNextSendInterval(int iSentSize); // 次回送信までの間隔 BOOL CanSendNow(DWORD dwNow); // 送信して良い時刻になったか BOOL KillSendFileThread(); // ★不要になったファイル送信スレッドを破棄する };

次に、SendRecvThread.cppを見てみましょう。
変更点には★を付けました。
メインスレッド(SimpleServer.cpp)からSendFile()が呼び出されたときに
CSendFileThreadを作ります。
DoWork()の中で不要になったCSendFileThreadを破棄します。

【SendRecvThread.cpp】 #include “SendRecvThread.h” #include “MySyncObject.h” // CMySyncObjectを使うため #include “RingBuff.h” // CRingBuffを使うため #include “SendFileThread.h” // ★CSendFileThreadを使うため // 送信速度 #define SEND_BPS (1000000.0) // 送信速度1Mbps //============================================== // function // コンストラクタ // parameter // ConnectionInfoRec *pConInfo [in]機能に必要な情報 // return // なし //============================================== CSendRecvThread::CSendRecvThread(ConnectionInfoRec *pConInfo) { m_pConInfo = pConInfo; m_fIamZombie = FALSE; m_pCRingBuffSend = new CRingBuff(SEND_BUFF_SIZE); // 送信リングバッファの構築 m_pCRingBuffRecv = new CRingBuff(RECV_BUFF_SIZE); // 受信リングバッファの構築 m_dwPrevSentTime = 0; // 初回送信はすぐに m_dwSendInterval = 0; // m_pCSendFileThread = NULL; // ★ファイル送信実施スレッド } //============================================== // function // デストラクタ // parameter // なし // return // なし //============================================== CSendRecvThread::~CSendRecvThread() { if (m_pCSendFileThread != NULL) // ★もし動いていたら止める(中断も必要でしょう) { m_pCSendFileThread->End(); m_pCSendFileThread->WaitForEnd(); } SAFE_DELETE(m_pCSendFileThread) SAFE_DELETE(m_pCRingBuffSend) // 送信リングバッファの破棄 SAFE_DELETE(m_pCRingBuffRecv) // 受信リングバッファの破棄 } //============================================== // function // 機能を記述した関数(DoSend, DoRecvの内容を記述する) // parameter // なし // return // 0:正常 -1:エラー発生 //============================================== UINT CSendRecvThread::DoWork() { BOOL fRet = TRUE; fd_set wfds, rfds; struct timeval tv; char *pcData = NULL; // 未送信データ int iSendSize = 0; // 未送信データサイズ char szRecvBuffer[RCVBUFSIZE]; // 受信データを一時的に格納 int iRecvSize; DWORD dwNow; // 送信チェックした時刻を覚えるため while (!m_fStopFlag) { KillSendFileThread(); // ★ファイル送信が終わっていたらスレッドを破棄 tv.tv_sec = 0; tv.tv_usec = 10 * 1000; // 10msec FD_ZERO(&rfds); FD_ZERO(&wfds); // 受信データ検査用fd_setは常にセットする FD_SET(m_pConInfo->fdClient, &rfds); // 送信可能時刻かつ未送信データがあるときだけ送信可能検査用fd_setにセットする // 送信データがあるかリングバッファを調べる iSendSize = m_pCRingBuffSend->GetReadableSize(); // チェックのために現在時刻を取得する timeBeginPeriod(1); // タイマーの最小精度を1msecにする dwNow = timeGetTime(); timeEndPeriod(1); // タイマーの最小精度を戻す // 送信可能時刻かつ未送信データがあるか if ((iSendSize > 0) && (CanSendNow(dwNow) == TRUE)) FD_SET(m_pConInfo->fdClient, &wfds); select(FD_SETSIZE, &rfds, &wfds, NULL, &tv); // タイムアウトまでSleepと同等 // 受信処理(DoRecv) if (FD_ISSET(m_pConInfo->fdClient, &rfds)) // 受信データがあればrecv実施 { // 受信リングバッファに空きがあれば取得する // ここで受信しなければ、次回のFD_ISSETで受信データありがセットされるので // 次回に取得することができる iRecvSize = min(m_pCRingBuffRecv->GetWriteableSize(), RCVBUFSIZE); if (iRecvSize > 0) { if ((iRecvSize = recv(m_pConInfo->fdClient, szRecvBuffer, iRecvSize, 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_pCRingBuffRecv->Write((LPBYTE)szRecvBuffer, iRecvSize); } } } // 受信リングバッファに格納されているデータの解析を行う // 複数のパケットが格納されている可能性があるので、FD_ISSETの結果とは無関係に // 解析を行うようにする if (AnalyzeDataRecv() == -1) // 返値が-1の時がエラー { m_pConInfo->pCMySyncObject->Lock(); DispErrorMsg(“Err:Packet format”); m_pConInfo->pCMySyncObject->UnLock(); fRet = FALSE; break; } // 送信処理(DoSend) if (FD_ISSET(m_pConInfo->fdClient, &wfds)) // 送信可能ならsend実施 { // 送信したいデータの取得(PATH_MTUより小さくなるように取得する) iSendSize = GetSendData(&pcData); if (send(m_pConInfo->fdClient, pcData, iSendSize, 0) != iSendSize) { DispErrorMsg(“Err:send\n”); fRet = FALSE; break; } SAFE_FREE(pcData) // 送信時刻と次回送信までの時間をセット m_dwPrevSentTime = dwNow; m_dwSendInterval = CalcNextSendInterval(iSendSize); } } m_pConInfo->pCMySyncObject->Lock(); m_fIamZombie = TRUE; m_pConInfo->pCMySyncObject->UnLock(); SAFE_FREE(pcData) 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; // 送信リングバッファに空きがないときは書き込まない fRet = m_pCRingBuffSend->Write((LPBYTE)pcData, iSize); return(fRet); } //============================================== // function // 送信データの取得 // parameter // char **ppcData [in/out]送信データ // return // データ長 //============================================== int CSendRecvThread::GetSendData(char **ppcData) { int iSize = 0; // 送信データがあるかリングバッファのデータサイズを調べる if ((iSize = m_pCRingBuffSend->GetReadableSize()) > 0) { // 送信サイズをPATH_MTUより小さくしておく(1024+共通ヘッダなら大丈夫) iSize = min(iSize, SENDBUFSIZE); *ppcData = (char *)calloc(iSize, sizeof(char)); iSize = m_pCRingBuffSend->Read((LPBYTE)*ppcData, iSize); } return(iSize); } //============================================== // function // このスレッドはゾンビ状態か // 別スレッドから参照される // parameter // なし // return // TRUE/FALSE //============================================== BOOL CSendRecvThread::IsZombie() { BOOL fRet; m_pConInfo->pCMySyncObject->Lock(); fRet = m_fIamZombie; m_pConInfo->pCMySyncObject->UnLock(); return(fRet); } //============================================== // 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; default: // 知らないコマンドなのでエラー iRet = -1; break; } L_END: return(iRet); } //============================================== // function // メッセージコマンドの受信 // parameter // HeaderRec *pHeader [in]仮読みしたヘッダ // return // 0:パケットが揃っていない // 1:パケットを受信したので処理を行った // -1:エラーが発生した //============================================== int CSendRecvThread::RecvMessagePacket(HeaderRec *pHeader) { int iRet = 0; MsgDataRec *pMsgData = NULL; int iMsgSize, iSize; char *pszMsg = NULL; LPBYTE pbDest = NULL; iMsgSize = ntohs(pHeader->wDataLen); // データが足りないときは何もしない iSize = m_pCRingBuffRecv->GetReadableSize(); if (iSize < iMsgSize + sizeof(HeaderRec)) goto L_END; // パケット全体を受信しているので読み込みを実施する pMsgData = (MsgDataRec *)calloc(iMsgSize + sizeof(HeaderRec), sizeof(BYTE)); m_pCRingBuffRecv->Read((LPBYTE)pMsgData, iMsgSize + sizeof(HeaderRec)); // NULLターミネート分を追加して確保 pszMsg = (char *)calloc(iMsgSize + 1, sizeof(char)); memcpy(pszMsg, pMsgData->bMsgData, iMsgSize); // UTF-8で受信したので、SJISに変換して表示する ConvUtf8toSJis((LPBYTE)pszMsg, NULL, &iSize); pbDest = (LPBYTE)calloc(iSize, sizeof(BYTE)); ConvUtf8toSJis((LPBYTE)pszMsg, pbDest, &iSize); m_pConInfo->pCMySyncObject->Lock(); fprintf(stderr, “Msg:recv %s\n”, pbDest); m_pConInfo->pCMySyncObject->UnLock(); SAFE_FREE(pMsgData) SAFE_FREE(pbDest) SAFE_FREE(pszMsg) iRet = 1; L_END: return(iRet); } //============================================== // function // 次の送信までの間隔(msec)を求める // parameter // なし // return // msec //============================================== DWORD CSendRecvThread::CalcNextSendInterval(int iSentSize) { DWORD dwInterval = 0; double dbBytePerSec = SEND_BPS / 8.0; double dbCountPerSec; if (iSentSize == 0) goto L_END; dbCountPerSec = dbBytePerSec / (double)iSentSize; dwInterval = DWORD(1000.0 / dbCountPerSec); L_END: return(dwInterval); } //============================================== // function // 今送信可能時刻か // parameter // DWORD dwNow [in]現在時刻 // return // TRUE/FALSE //============================================== BOOL CSendRecvThread::CanSendNow(DWORD dwNow) { BOOL fRet = FALSE; if (GetdwInterval(dwNow, m_dwPrevSentTime) >= m_dwSendInterval) fRet = TRUE; return(fRet); } //============================================== // function // ★ファイル送信の実施 // parameter // LPSTR pszFileName [in]ファイル名 // retun // TRUE/FALSE //============================================== BOOL CSendRecvThread::SendFile(LPSTR pszFileName) { BOOL fRet = FALSE; // ファイル送信スレッドが有効な時(送信中)は送信開始しない if (m_pCSendFileThread != NULL) { fprintf(stderr, “ファイル送信中 ファイル送信完了まで待ってください\n”); goto L_END; } m_pCSendFileThread = new CSendFileThread(this, pszFileName); m_pCSendFileThread->Begin(); fRet = TRUE; L_END: return(fRet); } //============================================== // function // ★ファイル送信済みのCSendFileThreadを破棄する // parameter // なし // return // TRUE:破棄した/FALSE:破棄すべきものがなかった //============================================== BOOL CSendRecvThread::KillSendFileThread() { BOOL fRet = FALSE; if (m_pCSendFileThread != NULL) { if (m_pCSendFileThread->IsZombie() == TRUE) { m_pCSendFileThread->End(); m_pCSendFileThread->WaitForEnd(); SAFE_DELETE(m_pCSendFileThread) fRet = TRUE; } } return(fRet); }

これで、ファイル送信の機能は完成です。
後は、メインスレッド(SimpleClient.cpp)から呼び出すために
メニューを追加して、メニューからファイル送信が選択されたら
ファイル送信開始メソッドを呼び出すようにすれば良いです。
SimpleClient.cppの変更点は以下の通りです。
メニューにファイル送信コマンドを追加するのは

【SimpleClient.cpp】 // メニューで使用する文字 #define CMD_QUIT_CHAR ‘q’ #define CMD_SEND_MSG_CHAR ‘m’ #define CMD_SEND_FILE_CHAR ‘s’ // ★ファイル送信開始 #define CMD_SEND_FILE_ABORT_CHAR ‘a’ // ★ファイル送信中断 //============================================== // function // メニューの表示 // parameter // なし // return // なし //============================================== void DispMenu() { fprintf(stderr, “\x1b[6;1H\x1b[0J”); // ★ファイル送信開始(s)を追加 fprintf(stderr, “%c:quit %c:メッセージ送信 %c:ファイル送信 : “, CMD_QUIT_CHAR, CMD_SEND_MSG_CHAR, CMD_SEND_FILE_CHAR); }

ファイル送信コマンドが選ばれたときにファイル名を取得するのは

【SimpleClient.cpp】 //============================================== // function // キーボード入力(改行まで)の取得 // 入力がないときはすぐにリターン // parameter // LPSTR pszString [in/out]データ格納用バッファ // int iSize [in]バッファのサイズ // return // 0:入力なし CMD_QUIT_CHAR:終了 CMD_SEND_MSG_CHAR:メッセージ送信 // CMD_SEND_FILE_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; // ★sコマンド入力時ファイルファイル名入力 case CMD_SEND_FILE_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_FILE_CHAR; } break; default: break; } } return(iRet); }

ファイル送信開始メソッドを呼び出すのは

【SimpleClient.cpp】 int main(int argc, char *argv[]) { char szKeyInBuff[81]; int iSize; LPBYTE pbDest = NULL; EnableESC(stderr); 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に文字列が格納されている // 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で実施 // メッセージパケットの送信 SendMessagePacket((char *)pbDest, iSize); SAFE_FREE(pbDest) // メニューの表示 DispMenu(); break; case CMD_SEND_FILE_CHAR: // ★’s’ファイル送信szKeyInBuffにファイル名が格納されている if (m_pCSendRecvThread != NULL) { m_pCSendRecvThread->SendFile(szKeyInBuff); } break; } } L_END: // 切断とすべてのソケットの破棄 Stop(); // ソケットライブラリの開放 UninitSocketLib(); m_pCMySyncObject->Uninitialize(); SAFE_DELETE(m_pCMySyncObject) return(0); }

これで、SimpleClientTransFile(Windows)にファイル送信機能が入りました。
いくつか積み残しはありますが、次回はSimpleServerTransFileにファイル
受信機能を実装することにしましょう。

SimpleClientTransFile for Win(ESCシーケンスによる画面制御がWindows7の

SimpleClientTransFile for Win
 ESCシーケンスによる画面制御がWindows7のコンソールでは機能しないので
 Locate()関数を作成して、有効・無効で動作を切り替えるようにしてあります。

SimpleClientTransFile for Linux
 Windows版と同じように作ったLinux版