前回作成したSimpleClientTrandFileに対応するSimpleServerTransFileを作成します。
SimpleServerTransFile for Linuxを素に作成することにしましょう。
define.hは前回作成したものと同じですので、コピーしてください。

各クライアントごとのスレッドでUDPによる受信を区別するためにポート番号
各スレッドで異なるものを指定できるようにします。
またクライアントに対してUDP送信できるようにアドレス情報を渡すようにします。
送受信スレッドに渡すパラメータにこれらを追加します。

【SendRecvThread.h】 typedef struct { SOCKET fdClient; // 接続済みソケット(acceptの結果) CMySyncObject *pCMySyncObject; // 同期オブジェクト WORD wUDPSrcPort; // ★UDP送信元ポート/0 sockaddr *ai_addr; // ★相手先アドレス size_t ai_addrlen; // ★相手先アドレス長 } ConnectionInfoRec;

送信元ポート番号(クライアントから見ると送信先)を各クライアントごとに異なるものに
する方法として、ポートを「明示的に指定する」/「動的に割り振る」、に対応する
ために、起動オプションに送信元ポート番号の開始番号を指定できるようにします。
指定しない時(0が設定された時)は動的に割り振ることにします。
このポート番号を送受信スレッドに渡すためにDoAccept関数に渡すようにします。

【SimpleServer.cpp】 // 関数の宣言 BOOL DoAccept(WORD wStartUDPSrcPort); // ★接続の受容(接続済みソケットを作成する)UDPの送信元ポート番号を指定可に int main(int argc, char *argv[]) { int iRet = -1; int ii; WORD wStartUDPSrcPort = 0; // ★UDPの送信元ポート番号を指定可に … // ★起動パラメータチェック UDP送信元開始ポート番号を追加 if ((argc != 2) && (argc != 3)) { fprintf(stderr, “Usage: %s <Server Port> [Start UDP Src Port]\n”, argv[0]); goto L_END; } // ★UDP送信元ポート 指定がないときはbindで決定される動的ポートを使用する if (argc == 3) wStartUDPSrcPort = (WORD)atol(argv[2]); … // 接続要求に対して接続済みソケットを作成する // 送受信スレッドの作成をゾンビスレッドの破棄を行う // ★送受信スレッド作成時にUDP送信元ポートを渡す if (DoAccept(wStartUDPSrcPort) == FALSE) goto L_END; … } //============================================== // function // ★接続要求の受け入れ(接続済みソケットの作成) // 送受信スレッドの管理 // parameter // WORD wStartUDPSrcPort [in]UDP送信元開始ポート番号/0 // return // TRUE/FALSE //============================================== BOOL DoAccept(WORD wStartUDPSrcPort) { … if (m_pCSendRecvThread[jj] == NULL) { m_pConInfo[jj] = (ConnectionInfoRec *)calloc(1, sizeof(ConnectionInfoRec)); m_pConInfo[jj]->pCMySyncObject = m_pCMySyncObject; m_pConInfo[jj]->fdClient = fdClient; if (wStartUDPSrcPort > 0) m_pConInfo[jj]->wUDPSrcPort = wStartUDPSrcPort + jj; // ★指定のUDP送信元ポートを使用 else m_pConInfo[jj]->wUDPSrcPort = 0; // ★動的UDP送信元ポートを使用 m_pConInfo[jj]->ai_addr = (sockaddr *)&ClntAddr; // ★UDP送信のために相手アドレスを渡す m_pConInfo[jj]->ai_addrlen = iClntLen; m_pCSendRecvThread[jj] = new CSendRecvThread(m_pConInfo[jj]); m_pCSendRecvThread[jj]->Begin(); fRet = TRUE; break; } … }

クライアントと同様にCSendRecvThreadクラスにUDPでの送受信用にソケット、
送受信リングバッファを追加します。
送信時、コントロールはTCPで行うので、強制的にTCPを使用する機能を追加します。
送信、受信をTCP・UDPで行うため、それぞれ送信関数、受信関数を作り、DoWork関数を
単純化します。

【SendRecvThread.h】 #pragma once … typedef struct { SOCKET fdClient; // 接続済みソケット(acceptの結果) CMySyncObject *pCMySyncObject; // 同期オブジェクト WORD wUDPSrcPort; // ★UDP送信元ポート/0 sockaddr *ai_addr; // ★相手先アドレス size_t ai_addrlen; // ★相手先アドレス長 } ConnectionInfoRec; class CSendRecvThread : public CThreadJob { public: … UINT DoWork() override; // DoRecvで実施している内容を記述 // ★送信データの設定 コントロール用に強制的にTCPで送信する機能を追加 BOOL SetSendData(char *pcData, int iSize, BOOL fForceTCP = FALSE); … private: … CRingBuff *m_pCRingBuffSend; // 送信データ格納用リングバッファ(TCP) CRingBuff *m_pCRingBuffRecv; // 受信データ格納用リングバッファ(TCP) CRingBuff *m_pCRingBuffSendUDP; // ★送信データ格納用リングバッファ(UDP) CRingBuff *m_pCRingBuffRecvUDP; // ★受信データ格納用リングバッファ(UDP) sockaddr_storage m_PeerAddr; // ★相手先情報(UDP送信用) socklen_t m_iPeerAddrLen; // ★相手先情報の長さ SOCKET m_fdUDP; // ★データ送受信方法がUDP使用時のソケット BYTE m_bTarnsMethod; // ★0:TCP 1:UDP DWORD m_dwPrevSentTime; // 前回送信した時刻(msec)TCP DWORD m_dwSendInterval; // 次回送信までの間隔(msec)TCP DWORD m_dwPrevSentTimeUDP; // ★前回送信した時刻(msec)UDP DWORD m_dwSendIntervalUDP; // ★次回送信までの間隔(msec)UDP … int GetSendData(char **ppcData, int iTcpUdp); // ★送信データの取得(TCP/UDPで参照バッファ切り替え) BOOL RecvTCPData(pollfd *fds); // ★TCP受信 BOOL RecvUDPData(pollfd *fds); // ★UDP受信 BOOL SendTCPData(pollfd *fds, DWORD dwNow); // ★TCP送信 BOOL SendUDPData(pollfd *fds, DWORD dwNow); // ★UDP送信 int AnalyzeDataRecv(int iTcpUdp); // ★受信データの解析(TCP/UDPで参照バッファ切り替え) int RecvMessagePacket(HeaderRec *pHeader, int iTcpUdp); // ★メッセージ受信(TCP/UDPで参照バッファ切り替え) int RecvFileSendPacket(HeaderRec *pHeader, int iTcpUdp); // ★ファイル送信コマンド受信関数(TCP/UDPで参照バッファ切り替え) int RecvFileRecvPacket(HeaderRec *pHeader, int iTcpUdp); // ★ファイル受信コマンド受信関数(TCP/UDPで参照バッファ切り替え) int RecvTransMethodPacket(HeaderRec *pHeader); // ★データ送受信方法受信関数(TCPのみ) BOOL MakePeerUDPSockaddr(WORD wPeerPort); // ★UDP通信用に相手先sockaddrを作る(ポート番号付与) DWORD CalcNextSendInterval(int iSentSize); // 次回送信までの間隔 BOOL CanSendNow(DWORD dwNow, int iTcpUdp); // ★送信して良い時刻になったか(TCP/UDPで参照変数切り替え) BOOL KillZombei(); // このクラスが作った不要(エラー発生)になったスレッドを破棄 };

CSendRecvThreadで作成したUDPソケットを破棄するための宣言をSendRecvThread.cppに
追加します。

【SendRecvThread.cpp】 extern BOOL DestroySocket(SOCKET &fd); // ★UDPソケットを破棄するため SimpleClietn.cppにある

UDP送受信のための変数の初期化をコンストラクタに追加します。
 ・送受信データ格納用リングバッファ作成
 ・送信帯域制御用変数の初期化
 ・UDPソケット格納用変数の初期化
 ・データ転送方法(TCP/UDP)格納用変数の初期化
 ・送信先アドレスの保存(ポート番号はUDP使用が決定した時に設定)

【SendRecvThread.cpp】 //============================================== // function // コンストラクタ // parameter // ConnectionInfoRec *pConInfo [in]機能に必要な情報 // return // なし //============================================== CSendRecvThread::CSendRecvThread(ConnectionInfoRec *pConInfo) { m_pConInfo = pConInfo; m_fIamZombie = FALSE; m_pCRingBuffSend = new CRingBuff(SEND_BUFF_SIZE); // 送信リングバッファの構築(TCP) m_pCRingBuffRecv = new CRingBuff(RECV_BUFF_SIZE); // 受信リングバッファの構築(TCP) m_pCRingBuffSendUDP = new CRingBuff(SEND_BUFF_SIZE);// ★送信リングバッファの構築(UDP) m_pCRingBuffRecvUDP = new CRingBuff(RECV_BUFF_SIZE);// ★受信リングバッファの構築(UDP) m_dwPrevSentTime = 0; // 初回送信はすぐに(TCP) m_dwSendInterval = 0; // m_dwPrevSentTimeUDP = 0; // ★初回送信はすぐに(UDP) m_dwSendIntervalUDP = 0; // ★ m_fdUDP = INVALID_SOCKET; // ★データ送受信UDP時のUDPソケット m_bTarnsMethod = 0; // ★0:TCP 1:UDP CTRL_TRANS_METHOD_REQ受信で確定 // ★相手の情報を覚える(ポートはTCPの接続で使用したものなのでUDP使用時に変更する) m_iPeerAddrLen = (socklen_t)m_pConInfo->ai_addrlen; memcpy(&m_PeerAddr, m_pConInfo->ai_addr, m_iPeerAddrLen); m_pCRecvFileThread = new CRecvFileThread(this); // ファイル受信スレッドを常に使えるように準備する m_pCRecvFileThread->Begin(); m_pCSendFileThread = new CSendFileThread(this); // ファイル送信スレッドを常に使えるように準備する m_pCSendFileThread->Begin(); m_pCChatMsgThread = new CChatMsgThread(this); // チャットメッセージ処理を常に使えるように準備する m_pCChatMsgThread->Begin(); }

UDP送受信のために追加した変数の開放をデストラクタに追加します。

【SendRecvThread.cpp】 //============================================== // function // デストラクタ // parameter // なし // return // なし //============================================== CSendRecvThread::~CSendRecvThread() { if (m_pCChatMsgThread != NULL) // このクラスが破棄されるときにチャットメッセージ処理スレッドを破棄 { m_pCChatMsgThread->End(); m_pCChatMsgThread->WaitForEnd(); } SAFE_DELETE(m_pCChatMsgThread) if (m_pCRecvFileThread != NULL) // このクラスが破棄されるときにファイル受信スレッドを破棄 { m_pCRecvFileThread->End(); m_pCRecvFileThread->WaitForEnd(); } SAFE_DELETE(m_pCRecvFileThread) if (m_pCSendFileThread != NULL) // このクラスが破棄されるときにファイル送信スレッドを破棄 { m_pCSendFileThread->End(); m_pCSendFileThread->WaitForEnd(); } SAFE_DELETE(m_pCRecvFileThread) SAFE_DELETE(m_pCRingBuffSend) // 送信リングバッファの破棄(TCP) SAFE_DELETE(m_pCRingBuffRecv) // 受信リングバッファの破棄(TCP) SAFE_DELETE(m_pCRingBuffSendUDP) // ★送信リングバッファの破棄(UDP) SAFE_DELETE(m_pCRingBuffRecvUDP) // ★受信リングバッファの破棄(UDP) DestroySocket(m_fdUDP); // ★UDPソケットの破棄 }

DoWorkで行っている送受信処理にUDPの送受信処理を追加します。
 ・受信については、TCPは常に受信します。
  UDPはデータ転送方法がUDPの時だけ受信します。
 ・送信については、コントロールはTCPでそのほかは、データ転送方法に応じて処理を
  行います。
  TCPは、送信リングバッファにデータがあれば常に送信します。
  UDPは、データ転送方法がUDPの時だけ送信します。
 ・可読性を良くするために、受信関数、送信関数を独立させます。

【SendRecvThread.cpp】 //============================================== // function // 機能を記述した関数 // parameter // なし // return // 0:正常 -1:エラー発生 //============================================== UINT CSendRecvThread::DoWork() { BOOL fRet = TRUE; pollfd fds[2] = { 0 }; // ★TCP, UDP用に配列を2に int iSendSize = 0; // 未送信データサイズ DWORD dwNow; // 送信チェックした時刻を覚えるため int iCheckfdNum; // ★poll対象のpollfdの数(TCP/UDP) fprintf(stderr, “DoWork()\n”); while (!m_fStopFlag) { // 致命的エラー発生のスレッドを破棄したときは終了 // 終了すると当該クライアントは切断される if (KillZombei() == TRUE) { fRet = FALSE; break; } memset(fds, 0, sizeof(fds)); iCheckfdNum = 1; // ★TCPソケットは必ず調査対象 fds[0].fd = m_pConInfo->fdClient; fds[0].events = POLLIN | POLLRDHUP; // 受信と相手側からの切断イベントを設定 // 送信データがあるかTCP送信リングバッファを調べる iSendSize = m_pCRingBuffSend->GetReadableSize(); // チェックのために現在時刻を取得する dwNow = timeGetTime(); // ★TCP 送信可能時刻かつ未送信データがあるときだけ送信可能検査 if ((iSendSize > 0) && (CanSendNow(dwNow, 0) == TRUE)) fds[0].events |= POLLOUT; // 書き込み可能を追加 // ★データ転送がUDPの時追加 if (m_bTarnsMethod == 1) { iCheckfdNum = 2; fds[1].fd = m_fdUDP; fds[1].events = POLLIN | POLLRDHUP; // 受信と相手側からの切断イベントを設定 // 送信データがあるかUDP送信リングバッファを調べる iSendSize = m_pCRingBuffSendUDP->GetReadableSize(); if ((iSendSize > 0) && (CanSendNow(dwNow, 1) == TRUE)) fds[1].events |= POLLOUT; // 書き込み可能を追加 } // 検査の実施(TCP, UDP) poll(fds, iCheckfdNum, 10); // ★受信処理TCP if ((fRet = RecvTCPData(fds)) == FALSE) break; // ★受信処理UDP if ((fRet = RecvUDPData(fds)) == FALSE) break; // 受信リングバッファに格納されているデータの解析を行う // 複数のパケットが格納されている可能性があるので、reventsの結果とは無関係に // 解析を行うようにする // ★TCP受信データの解析 if (AnalyzeDataRecv(0) == -1) { DispErrorMsg(“Err:Packet format”); fRet = FALSE; break; } // ★UDP受信データの解析 if (AnalyzeDataRecv(1) == -1) { DispErrorMsg(“Err:Packet format”); fRet = FALSE; break; } // ★送信処理(TCP) if ((fRet = SendTCPData(fds, dwNow)) == FALSE) break; // ★送信処理(UDP) if ((fRet = SendUDPData(fds, dwNow)) == FALSE) break; } m_pConInfo->pCMySyncObject->Lock(); m_fIamZombie = TRUE; m_pConInfo->pCMySyncObject->UnLock(); return((fRet == TRUE) ? 0 : -1); }

可読性を良くするために、独立させた受信関数(TCP、UDP)、送信関数(TCP、UDP)を
追加します。

【SendRecvThread.cpp】 //============================================== // function // ★TCPの受信 // 受信リングバッファに格納 // parameter // pollfd *fds [in] // return // TRUE/FALSE:切断/エラー //============================================== BOOL CSendRecvThread::RecvTCPData(pollfd *fds) { BOOL fRet = TRUE; char szRecvBuffer[RCVBUFSIZE]; // 受信データを一時的に格納 int iRecvSize; if (fds[0].revents & POLLRDHUP) // 相手側からの切断 { fprintf(stderr, “Disconnected pollrdhup\n”); fRet = FALSE; } if (fds[0].revents & POLLERR) // エラー発生 { DispErrorMsg(“Err:DoWork”); fRet = FALSE; } // 受信処理 if (fds[0].revents & POLLIN) // 受信データイベント { // 受信リングバッファに空きがあれば受信する // ここで受信しなければ、次回のrevents検査で受信が行われる iRecvSize = min(m_pCRingBuffRecv->GetWriteableSize(), RCVBUFSIZE); if (iRecvSize > 0) { if ((iRecvSize = recv(m_pConInfo->fdClient, szRecvBuffer, iRecvSize, 0)) <= 0) { if (iRecvSize == 0) DispErrorMsg(“Disconnected recv”); else DispErrorMsg(“Err:recv”); fRet = FALSE; } else { fprintf(stderr, “recv socket:%d\n:”, m_pConInfo->fdClient); // 取得したデータすぐに受信リングバッファに書きこむ // 受信リングバッファに書き込むのはこのスレッドだけなので、すべて書き込めるはず m_pCRingBuffRecv->Write((LPBYTE)szRecvBuffer, iRecvSize); } } } return(fRet); } //============================================== // function // ★UDPの受信 // 受信リングバッファに格納 // parameter // pollfd *fds [in] // return // TRUE/FALSE:切断/エラー //============================================== BOOL CSendRecvThread::RecvUDPData(pollfd *fds) { BOOL fRet = TRUE; char szRecvBuffer[RCVBUFSIZE]; // 受信データを一時的に格納 int iRecvSize; sockaddr_storage PeerAddr; // 送信元相手アドレス情報 socklen_t iPeerLen; if (fds[1].revents & POLLRDHUP) // 相手側からの切断 { fprintf(stderr, “Disconnected pollrdhup\n”); fRet = FALSE; } if (fds[1].revents & POLLERR) // エラー発生 { DispErrorMsg(“Err:DoWork”); fRet = FALSE; } // 受信処理 if (fds[1].revents & POLLIN) // 受信データイベント { // UDPではパケットサイズより小さいサイズで受信すると残りは破棄される // ここで受信しなければ、次回の検査で受信が行われる if (m_pCRingBuffRecvUDP->GetWriteableSize() > RCVBUFSIZE) { iPeerLen = sizeof(PeerAddr); // 送信元アドレス情報を格納する構造体のサイズ if ((iRecvSize = recvfrom(m_fdUDP, szRecvBuffer, RCVBUFSIZE, 0, (sockaddr *)&PeerAddr, &iPeerLen)) < 0) { DispErrorMsg(“Err:recvfrom”); fRet = FALSE; } else { char szPeerAddr[NI_MAXHOST]; // 相手アドレス,ポート番号 char szServiceNo[NI_MAXSERV]; // をgetnameinfoで取得 // 送信元の表示 getnameinfo((sockaddr *)&PeerAddr, iPeerLen, szPeerAddr, NI_MAXHOST, szServiceNo, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV); fprintf(stderr, “recvfrom socket:%d Addr:%s Port:%s\n:”, m_fdUDP, szPeerAddr, szServiceNo); // NAT越え対応のため送信元(サーバからの送信先)をこれにする memcpy(&m_PeerAddr, &PeerAddr, iPeerLen); m_iPeerAddrLen = iPeerLen; // 取得したデータすぐに受信リングバッファに書きこむ // 受信リングバッファに書き込むのはこのスレッドだけなので、すべて書き込めるはず m_pCRingBuffRecvUDP->Write((LPBYTE)szRecvBuffer, iRecvSize); } } } return(fRet); } //============================================== // function // ★TCPの送信 // 送信リングバッファのデータを送信する // parameter // pollfd *fds [in] // DWORD dwNow [in]現在時刻 // return // TRUE/FALSE:切断/エラー //============================================== BOOL CSendRecvThread::SendTCPData(pollfd *fds, DWORD dwNow) { BOOL fRet = TRUE; char *pcData = NULL; // 未送信データ int iSendSize = 0; // 未送信データサイズ // 送信処理 if (fds[0].revents & POLLOUT) // 送信可能ならsend実施 { // 送信したいデータの取得(PATH_MTUより小さくなるように取得する) iSendSize = GetSendData(&pcData, 0); if (send(m_pConInfo->fdClient, pcData, iSendSize, 0) != iSendSize) { DispErrorMsg(“Err:send”); fRet = FALSE; } SAFE_FREE(pcData) // 送信時刻と次回送信までの時間をセット m_dwPrevSentTime = dwNow; m_dwSendInterval = CalcNextSendInterval(iSendSize); } SAFE_FREE(pcData) return(fRet); } //============================================== // function // ★UDPの送信 // 送信リングバッファのデータを送信する // parameter // pollfd *fds [in] // DWORD dwNow [in]現在時刻 // return // TRUE/FALSE:切断/エラー //============================================== BOOL CSendRecvThread::SendUDPData(pollfd *fds, DWORD dwNow) { BOOL fRet = TRUE; char *pcData = NULL; // 未送信データ int iSendSize = 0; // 未送信データサイズ // 送信処理 if ((m_bTarnsMethod == 1) && (fds[1].revents & POLLOUT)) { // 送信したいデータの取得(PATH_MTUより小さくなるように取得する) iSendSize = GetSendData(&pcData, 1); if (sendto(m_fdUDP, pcData, iSendSize, 0, (sockaddr *)&m_PeerAddr, m_iPeerAddrLen) != iSendSize) { DispErrorMsg(“Err:sendto”); fRet = FALSE; } SAFE_FREE(pcData) // 送信時刻と次回送信までの時間をセット m_dwPrevSentTimeUDP = dwNow; m_dwSendIntervalUDP = CalcNextSendInterval(iSendSize); } SAFE_FREE(pcData) return(fRet); }

送信リングバッファにデータを書き込む関数にTCP, UDPの切り替え機能を追加します。
コントロールは常にTCPで行うの強制的にTCPの送信リングバッファに書きこめる機能も
追加します。

【SendRecvThread.cpp】 //============================================== // function // ★送信データの設定(TCP/UDP) // parameter // char *pcData [in]送信データ // int iSize [in]データ長 // BOOL fForceTCP [in]必ずTCP(コントロール) // return // TRUE/FALSE //============================================== BOOL CSendRecvThread::SetSendData(char *pcData, int iSize, BOOL fForceTCP) { BOOL fRet = FALSE; CRingBuff *pCRingBuff = ((fForceTCP == FALSE) && (m_bTarnsMethod == 1)) ? m_pCRingBuffSendUDP : m_pCRingBuffSend; // 送信リングバッファに空きがないときは書き込まない fRet = pCRingBuff->Write((LPBYTE)pcData, iSize); return(fRet); }

送信リングバッファからデータを読み込む関数にTCP, UDPの切り替え機能を追加します。

【SendRecvThread.cpp】 //============================================== // function // ★送信データの取得(TCP/UDP) // parameter // char **ppcData [in/out]送信データ // int iTcpUdp [in]0:TCP 1:UDP // return // データ長 //============================================== int CSendRecvThread::GetSendData(char **ppcData, int iTcpUdp) { int iSize = 0; CRingBuff *pCRingBuff = (iTcpUdp == 1) ? m_pCRingBuffSendUDP : m_pCRingBuffSend; // 送信データがあるかリングバッファのデータサイズを調べる if ((iSize = pCRingBuff->GetReadableSize()) > 0) { // 送信サイズをPATH_MTUより小さくしておく(1024+共通ヘッダなら大丈夫) iSize = min(iSize, SENDBUFSIZE); *ppcData = (char *)calloc(iSize, sizeof(char)); iSize = pCRingBuff->Read((LPBYTE)*ppcData, iSize); } return(iSize); }

受信データの解析関数に対象とする受信リングバッファをTCP, UDPで切り替え機能を
追加します。
各機能を実現するスレッドのバッファにデータを書き込む処理もTCP, UDPで切り替え
ようにします。ただしコントロールについては、TCPのみ処理を行います。

【SendRecvThread.cpp】 //============================================== // function // ★受信データの解析 // 受信リングバッファに格納されているデータを調べる // parameter // int iTcpUdp [in]0:TCP 1:UDP // return // 0:パケットが揃っていない // 1:パケットを受信したので処理を行った // -1:エラーが発生した //============================================== BOOL CSendRecvThread::AnalyzeDataRecv(int iTcpUdp) { int iRet = 0; HeaderRec Header; WORD wCmd; // データサイズを調べる CRingBuff *pCRingBuff = (iTcpUdp == 1) ? m_pCRingBuffRecvUDP : m_pCRingBuffRecv; int iSize = pCRingBuff->GetReadableSize(); if (iSize < sizeof(HeaderRec)) // ヘッダサイズに満たないときは何もしない goto L_END; // ヘッダ部を借り読み込みする pCRingBuff->ReadWithoutUpdateHeadPoint((LPBYTE)&Header, sizeof(HeaderRec)); // ヘッダ部の解析 if (memcmp(Header.bMagicData, MAGIC_STRING, strlen(MAGIC_STRING)) != 0) { iRet = -1; // 識別子が違うのでエラー goto L_END; } wCmd = ntohs(Header.wCommand); Locate(1, 10, 1); fprintf(stderr, “CMD:%d”, wCmd); switch (wCmd) { case CMD_MSG_DATA: iRet = RecvMessagePacket(&Header, iTcpUdp); break; // ファイル送信コマンド(C->S)を処理する case CMD_SEND_FILE_START_REQ: case CMD_SEND_FILE_ING: case CMD_SEND_FILE_END_REQ: case CMD_SEND_FILE_ABORT_REQ: iRet = RecvFileSendPacket(&Header, iTcpUdp); break; // ファイル受信コマンド(C->S)を処理する case CMD_RECV_FILE_SIZE_REQ: case CMD_RECV_FILE_START_REQ: case CMD_RECV_FILE_END_RES: case CMD_RECV_FILE_ABORT_REQ: iRet = RecvFileRecvPacket(&Header, iTcpUdp); break; case CTRL_TRANS_METHOD_REQ: // ★データ送信方法のネゴシエーション if (iTcpUdp == 0) // TCPのみ iRet = RecvTransMethodPacket(&Header); else iRet = -1; break; default: // 知らないコマンド iRet = -1; break; } L_END: return(iRet); }

各機能を実現するスレッドのバッファにデータを書き込む処理にTCP, UDPで切り替える
機能を追加します。

【SendRecvThread.cpp】 //============================================== // function // ★メッセージコマンドの受信 // parameter // HeaderRec *pHeader [in]仮読みしたヘッダ // int iTcpUdp [in]0:TCP 1:UDP // return // 0:パケットが揃っていない // 1:パケットを受信したので処理を行った // -1:エラーが発生した //============================================== BOOL CSendRecvThread::RecvMessagePacket(HeaderRec *pHeader, int iTcpUdp) { int iRet = 0; int iDataSize, iSize, iPacketSize; LPBYTE pbPacket = NULL; CRingBuff *pCRingBuff = (iTcpUdp == 1) ? m_pCRingBuffRecvUDP : m_pCRingBuffRecv; iDataSize = ntohs(pHeader->wDataLen); iPacketSize = iDataSize + sizeof(HeaderRec); iSize = pCRingBuff->GetReadableSize(); if (iSize < iPacketSize) goto L_END; pbPacket = (LPBYTE)calloc(iPacketSize, sizeof(BYTE)); // パケット全体を仮読み pCRingBuff->ReadWithoutUpdateHeadPoint(pbPacket, iPacketSize); if (m_pCChatMsgThread != NULL) { // チャットメッセージ処理スレッドに渡せたら読み込んだことにする if (m_pCChatMsgThread->m_pCRingBuffCmd->Write(pbPacket, iPacketSize) == TRUE) { pCRingBuff->UpdateHeadPoint(iPacketSize); iRet = 1; } } else { // チャットメッセージ処理スレッドがいなければエラー切断 pCRingBuff->UpdateHeadPoint(iPacketSize); iRet = -1; } SAFE_FREE(pbPacket); L_END: return(iRet); } //============================================== // function // ★ファイル送信コマンドの受信 // ファイル送信コマンド(C->S)をCRecvThreadに渡すだけ // parameter // HeaderRec *pHeader [in]仮読みしたヘッダ // int iTcpUdp [in]0:TCP 1:UDP // return // 0:パケットが揃っていない, ファイル受信側が満杯 // 1:パケットを受信したので処理を行った // -1:エラーが発生した //============================================== int CSendRecvThread::RecvFileSendPacket(HeaderRec *pHeader, int iTcpUdp) { int iRet = 0; int iDataSize, iSize, iPacketSize; LPBYTE pbPacket = NULL; CRingBuff *pCRingBuff = (iTcpUdp == 1) ? m_pCRingBuffRecvUDP : m_pCRingBuffRecv; iDataSize = ntohs(pHeader->wDataLen); iPacketSize = iDataSize + sizeof(HeaderRec); iSize = pCRingBuff->GetReadableSize(); if (iSize < iPacketSize) goto L_END; pbPacket = (LPBYTE)calloc(iPacketSize, sizeof(BYTE)); // パケット全体を仮読み pCRingBuff->ReadWithoutUpdateHeadPoint(pbPacket, iPacketSize); if (m_pCRecvFileThread != NULL) { // ファイル受信スレッドに渡せたら読み込んだことにする if (m_pCRecvFileThread->m_pCRingBuffCmd->Write(pbPacket, iPacketSize) == TRUE) { pCRingBuff->UpdateHeadPoint(iPacketSize); iRet = 1; } } else { // ファイル受信スレッドがいなければエラー切断 pCRingBuff->UpdateHeadPoint(iPacketSize); iRet = -1; } SAFE_FREE(pbPacket); L_END: return(iRet); } //============================================== // function // ★ファイル受信コマンドの受信 // ファイル受信コマンド(C->S)をCSendThreadに渡すだけ // parameter // HeaderRec *pHeader [in]仮読みしたヘッダ // int iTcpUdp [in]0:TCP 1:UDP // return // 0:パケットが揃っていない, ファイル受信側が満杯 // 1:パケットを受信したので処理を行った // -1:エラーが発生した //============================================== int CSendRecvThread::RecvFileRecvPacket(HeaderRec *pHeader, int iTcpUdp) { int iRet = 0; int iDataSize, iSize, iPacketSize; LPBYTE pbPacket = NULL; CRingBuff *pCRingBuff = (iTcpUdp == 1) ? m_pCRingBuffRecvUDP : m_pCRingBuffRecv; iDataSize = ntohs(pHeader->wDataLen); iPacketSize = iDataSize + sizeof(HeaderRec); iSize = pCRingBuff->GetReadableSize(); if (iSize < iPacketSize) goto L_END; pbPacket = (LPBYTE)calloc(iPacketSize, sizeof(BYTE)); // パケット全体を仮読み pCRingBuff->ReadWithoutUpdateHeadPoint(pbPacket, iPacketSize); if (m_pCSendFileThread != NULL) { // ファイル送信スレッドに渡せたら読み込んだことにする if (m_pCSendFileThread->m_pCRingBuffCmd->Write(pbPacket, iPacketSize) == TRUE) { pCRingBuff->UpdateHeadPoint(iPacketSize); iRet = 1; } } else { // ファイル受信スレッドがいなければエラー切断 pCRingBuff->UpdateHeadPoint(iPacketSize); iRet = -1; } SAFE_FREE(pbPacket); L_END: return(iRet); }

データ転送方法応答の内容に従ってデータ転送方法応答を処理する関数を追加します。
 ・転送方法変数の変更
   UDP:UDPソケットの作成
     受信のためにAI_PASSIVE, 送信元ポート番号でbind
     sendtoで使用するアドレスの作成
   TCP:なにもしない
 ・応答データの送信

【SendRecvThread.cpp】 //============================================== // function // ★データ送受信方法の受信 // UDPの時は使えるかを調べてOKならソケットを作成して応答する // TCPの時は無条件に応答する // parameter // HeaderRec *pHeader [in]仮読みしたヘッダ // return // 0:パケットが揃っていない, ファイル受信側が満杯 // 1:パケットを受信したので処理を行った // -1:エラーが発生した //============================================== int CSendRecvThread::RecvTransMethodPacket(HeaderRec *pHeader) { int iRet = 0; int iDataSize, iSize, iPacketSize; LPBYTE pbPacket = NULL; TransMethodReqRec *pTransMethod; addrinfo hints, *pres = NULL; char szPort[NI_MAXSERV], szAddr[NI_MAXHOST]; int on; sockaddr_storage Addr; socklen_t iAddrLen; 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->Read(pbPacket, iPacketSize); pTransMethod = (TransMethodReqRec *)pbPacket; // 変更なしの場合(こないはず) if (m_bTarnsMethod == pTransMethod->TansMethodInfo.bMethod) goto L_SEND_RES; if (pTransMethod->TansMethodInfo.bMethod == 1) { DestroySocket(m_fdUDP); // UDPソケットを作成する if ((m_fdUDP = socket(m_PeerAddr.ss_family, SOCK_DGRAM, 0)) == INVALID_SOCKET) { m_bTarnsMethod = 0; goto L_SEND_RES; } // bindする memset(&hints, 0, sizeof(hints)); hints.ai_family = m_PeerAddr.ss_family; hints.ai_socktype = SOCK_DGRAM; // UDP // すべてのインターフェースで受信可 hints.ai_flags = AI_PASSIVE; DISABLE_C4996 sprintf(szPort, “%d”, m_pConInfo->wUDPSrcPort); // ポート番号 ENABLE_C4996 if (getaddrinfo(NULL, szPort, &hints, &pres) != 0) { m_bTarnsMethod = 0; goto L_SEND_RES; } #ifdef IPV6_V6ONLY // IPv6ソケットでIPv4射影アドレスを使用しないように設定 if (m_PeerAddr.ss_family == AF_INET6) { on = 1; if (setsockopt(m_fdUDP, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on, sizeof(on)) < 0) { m_bTarnsMethod = 0; goto L_SEND_RES; } } #endif if (bind(m_fdUDP, pres->ai_addr, (int)pres->ai_addrlen) == SOCKET_ERROR) { m_bTarnsMethod = 0; goto L_SEND_RES; } // m_pConInfo->wUDPSrcPortが0のときは、動的なポートが割り当てられるので調べる iAddrLen = sizeof(Addr); getsockname(m_fdUDP, (sockaddr *)&Addr, &iAddrLen); getnameinfo((sockaddr *)&Addr, iAddrLen, szAddr, sizeof(szAddr), szPort, sizeof(szPort), NI_NUMERICHOST | NI_NUMERICSERV); // UDP通信用に相手先sockaddrを作る // クライアント側でNAT越えがあったときのために、UDPパケット受信で相手先sockaddrを更新する m_bTarnsMethod = MakePeerUDPSockaddr(ntohs(pTransMethod->TansMethodInfo.wUDPSrcPort)); } else m_bTarnsMethod = 0; L_SEND_RES: // 応答パケットを返す // パケット全体のエリアを確保 pTransMethod = (TransMethodReqRec *)calloc(iPacketSize, sizeof(BYTE)); memcpy(pTransMethod->header.bMagicData, MAGIC_STRING, strlen(MAGIC_STRING)); pTransMethod->header.wCommand = htons(CTRL_TRANS_METHOD_RES); pTransMethod->header.wDataLen = htons(sizeof(TransMethodInfoRec)); pTransMethod->TansMethodInfo.bMethod = m_bTarnsMethod; pTransMethod->TansMethodInfo.wUDPSrcPort = htons(atol(szPort)); iRet = (SetSendData((char *)pTransMethod, iPacketSize, TRUE) == TRUE) ? 1 : -1; SAFE_FREE(pTransMethod) L_END: SAFE_FREE(pbPacket); if (pres != NULL) freeaddrinfo(pres); if (m_bTarnsMethod == 0) DestroySocket(m_fdUDP); return(iRet); }

データ転送方法応答がUDPのときsendtoで使用するアドレス情報を作成する関数
を追加します。
 アドレスはコンストラクタで設定済みなので、ここではポートを変更します。

【SendRecvThread.cpp】 //============================================== // function // ★UDP通信用に相手先sockaddrを作る // m_PeerAddrのポート番号を変更 // parameter // WORD wPeerPort [in]相手先ポート // return // TRUE/FALSE //============================================== BOOL CSendRecvThread::MakePeerUDPSockaddr(WORD wPeerPort) { BOOL fRet = FALSE; char szAddr[NI_MAXHOST], szPort[NI_MAXSERV]; addrinfo hints, *pres = NULL; getnameinfo((sockaddr *)&m_PeerAddr, m_iPeerAddrLen, szAddr, sizeof(szAddr), NULL, 0, NI_NUMERICHOST); DISABLE_C4996 sprintf(szPort, “%d”, wPeerPort); // ポート番号 ENABLE_C4996 memset(&hints, 0, sizeof(hints)); if (getaddrinfo(szAddr, szPort, &hints, &pres) == 0) { memcpy(&m_PeerAddr, pres->ai_addr, pres->ai_addrlen); m_iPeerAddrLen = (socklen_t)pres->ai_addrlen; freeaddrinfo(pres); fRet = TRUE; } return(fRet); }

送信可能時刻になったかを調べる関数にTCP, UDPの切り替え機能を追加します。

【SendRecvThread.cpp】 //============================================== // function // ★今送信可能時刻か // parameter // DWORD dwNow [in]現在時刻 // int iTcpUdp [in]0:TCP 1:UDP // retun // TRUE/FALSE //============================================== BOOL CSendRecvThread::CanSendNow(DWORD dwNow, int iTcpUdp) { BOOL fRet = FALSE; DWORD dwPrevSentTime = (iTcpUdp == 1) ? m_dwPrevSentTimeUDP : m_dwPrevSentTime; DWORD dwSendInterval = (iTcpUdp == 1) ? m_dwSendIntervalUDP : m_dwSendInterval; if (GetdwInterval(dwNow, dwPrevSentTime) >= dwSendInterval) fRet = TRUE; return(fRet); }

これで完成です。
Windows版も同様の考え方で作成することができます。
これらのプログラムの完全なプロジェクトは以下のリンクから入手できます。
SimpleServerTransFile for Windows TCP/UDP対応
SimpleServerTransFile for Linux TCP/UDP対応

次回は、SimpleClientTransFile と SimpleServerTransFile を実際にうごかしてみて
データ転送方法が切り替わることを確認します。
またSimpleServerTransFile をインターネット上に設置してUDPでのNAT越えについても
見てみることにしましょう。