TCPによるサーバ・クライアントプログラムとUDPによるプログラムの
概要を比較してみます。
最初に次のような、UDPクライアントを作成します。
これを使って、UDPクライアントがサーバのように動作することを確認してみましょう。
このクライアントでは送信元ポートをbind(指定)できるようにします。
プログラム起動引数 送信先アドレス 送信先ポート番号 [送信元ポート番号] メニュー m:メッセージ送信 q:終了
先ずはWindows版UDPクライアントプログラムを作成しましょう。
ファイル→新規作成→プロジェクト→VisualC++→空のプロジェクト
名前:UDPClient
ソリューションプラットフォームはx64(またはx86)
SimpleClientTransFile for Windowsから流用できるソースをコピーして追加します。
define.h
MySyncObject.h, MySyncObject.cpp
RingBuff.h, RingBuff.cpp
SendRecvThread.h, SendRecvThread.cpp
stdThread.h, stdThread.cpp
ThreadJob.h, ThreadJob.cpp
SimpleClient.cpp
SimpleClient.cppをUDPClient.cppに名前を変更しTCP部分、機能を変更します。
CSendRecvThreadのTCP部分、機能を変更します
UDPでは送信時に送信先を指定する(sendto)必要があるので送受信スレッドに
渡すパラメータConnectionInfoRecを変更します。
【SendRecvThread.h】 typedef struct { SOCKET fdClient; CMySyncObject *pCMySyncObject; // ★送信先アドレス情報(sendtoのパラメータ)を追加 sockaddr *ai_addr; size_t ai_addrlen; } ConnectionInfoRec;
SimpleClient.cppから名前を変更したUDPClient.cppについて、以下の変更を行います。
今回使用しない機能を削除し、TCP部分をUDPに変更します。
具体的には以下のような項目です。
・メニュー文字から不要なものを削除します。
・ソケット作成部分をTCPからUDPに変更します。
・ShowPathMTU()を削除します。
これらの変更を行った後のUDPClinet.cppは次のようになります。
変更部分には★印を記しています。
【UDPClient.cpp】 #include “stdThread.h” #include <conio.h> // キー入力検査のため #include “SendRecvThread.h” // 送受信スレッドを使うため #include “MySyncObject.h” // 同期オブジェクトを使うため #include “define.h” #include <io.h> // ESCシーケンス画面制御のため // メニューで使用する文字 #define CMD_QUIT_CHAR ‘q’ #define CMD_SEND_MSG_CHAR ‘m’ // 関数の宣言 BOOL InitSocketLib(); // WinSockDLLの初期化 BOOL UninitSocketLib(); // WinSockDLLの終了 BOOL CreateSocket(LPCSTR szAddress, WORD wServerPort, WORD wClientPort); // ★UDPソケットの作成 BOOL DestroySocket(SOCKET &fd); // 切断とソケットの破棄 void Stop(); // すべてのソケットを破棄する void DispMenu(); // メニューの表示 int GetKeyString(LPSTR pszString, int iSize); // キーボード入力文字の取得 BOOL KillZombei(); // ゾンビ状態のスレッドを破棄する BOOL ShowInterfaceIndex(addrinfo *pAddr); // インターフェースを表示する BOOL SetHopLimit(SOCKET fd, int iFamily, int iLimit); // ホップリミットを設定する BOOL SendMessagePacket(char *pcData, int iSize); // ★メッセージパケットの送信 // 変数の宣言 CSendRecvThread *m_pCSendRecvThread = NULL; ConnectionInfoRec *m_pConInfo = NULL; CMySyncObject *m_pCMySyncObject = NULL; int main(int argc, char *argv[]) { char szKeyInBuff[81]; int iSize; LPBYTE pbDest = NULL; WORD wClientPort = 0; // ★送信元ポート EnableESC(stderr); m_pCMySyncObject = new CMySyncObject(); m_pCMySyncObject->Initialize(); // ★起動パラメータチェック 送信元ポートを指定可能に if ((argc != 3) && (argc != 4)) { fprintf(stderr, “Usage: %s <ServerAddress> <ServerPort> [ClientPort]\n”, argv[0]); goto L_END; } if (argc == 4) wClientPort = (WORD)atol(argv[3]); // ソケットライブラリの初期化 if (InitSocketLib() == FALSE) goto L_END; // ★ソケットの作成 送受信スレッドを開始する if (CreateSocket(argv[1], (WORD)atol(argv[2]), wClientPort) == 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; } } L_END: // すべてのソケットの破棄 Stop(); // ソケットライブラリの開放 UninitSocketLib(); m_pCMySyncObject->Uninitialize(); SAFE_DELETE(m_pCMySyncObject) return(0); } // 変更なし BOOL InitSocketLib() // 変更なし BOOL UninitSocketLib() //============================================== // function // ★UDPソケットの作成 // 送受信スレッドを開始する // parameter // LPCSTR szAddress [in]送信先アドレス // WORD wServerPort [in]送信先ポート // WORD wClientPort [in]送信元ポート/0 // return // TRUE/FALSE //============================================== BOOL CreateSocket(LPCSTR szAddress, WORD wServerPort, WORD wClientPort) { BOOL fRet = TRUE; SOCKET fd = INVALID_SOCKET; addrinfo hints, *pres = NULL, *pTemp = NULL, *pres2 = NULL; char szPort[NI_MAXSERV]; int on, iRet; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_DGRAM; // ★UDP DISABLE_C4996 sprintf(szPort, “%d”, wServerPort); // ポート番号=サービス ENABLE_C4996 // アドレスを指定してgetaddrinfoを実行 if (getaddrinfo(szAddress, szPort, &hints, &pres) == 0) { pTemp = pres; while (pTemp != NULL) { // socketを実行 if ((fd = socket(pTemp->ai_family, pTemp->ai_socktype, pTemp->ai_protocol)) == INVALID_SOCKET) goto L_NEXT; // 使用するインターフェースを表示する ShowInterfaceIndex(pTemp); // HopLimitを取得設定する SetHopLimit(fd, pTemp->ai_family, 10); // ★socketが作れたのでこのソケット採用することにして抜ける fRet = TRUE; break; L_NEXT: pTemp = pTemp->ai_next; } } if (fd == INVALID_SOCKET) // ソケット作成に失敗 { DispErrorMsg(“Err:socket”); fRet = FALSE; goto L_END; } // ★送信元ポートが指定されたときはbindを実施する if (wClientPort != 0) { memset(&hints, 0, sizeof(hints)); // 使用するアドレス情報と同じ条件でポート番号だけ送信元にする hints.ai_family = pTemp->ai_family; hints.ai_socktype = pTemp->ai_socktype; hints.ai_protocol = pTemp->ai_protocol; // すべてのインターフェースで受信可 hints.ai_flags = AI_PASSIVE; DISABLE_C4996 sprintf(szPort, “%d”, wClientPort); // ポート番号=サービス ENABLE_C4996 if (getaddrinfo(NULL, szPort, &hints, &pres2) == 0) { #ifdef IPV6_V6ONLY // IPv6ソケットでIPv4射影アドレスを使用しないように設定 if (pTemp->ai_family == AF_INET6) { on = 1; if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&on, sizeof(on)) < 0) { DispErrorMsg(“Err:setsockopt”); fRet = FALSE; goto L_END; } } #endif // ★UDPではSO_REUSEADDRを使わない if ((iRet = bind(fd, pres2->ai_addr, (int)pres2->ai_addrlen)) == SOCKET_ERROR) { DispErrorMsg(“Err:bind”); fRet = FALSE; goto L_END; } } } // 送受信スレッドを開始する m_pConInfo = (ConnectionInfoRec *)calloc(1, sizeof(ConnectionInfoRec)); m_pConInfo->fdClient = fd; m_pConInfo->ai_addr = (sockaddr *)calloc(pTemp->ai_addrlen, sizeof(BYTE)); // ★ m_pConInfo->ai_addrlen = pTemp->ai_addrlen; memcpy(m_pConInfo->ai_addr, pTemp->ai_addr, pTemp->ai_addrlen); m_pConInfo->pCMySyncObject = m_pCMySyncObject; m_pCSendRecvThread = new CSendRecvThread(m_pConInfo); m_pCSendRecvThread->Begin(); L_END: if (pres != NULL) freeaddrinfo(pres); if (pres2 != NULL) freeaddrinfo(pres2); if((fRet == FALSE) && (fd != INVALID_SOCKET)) { DestroySocket(fd); } return(fRet); } //変更なし BOOL DestroySocket(SOCKET &fd) //============================================== // function // 送受信スレッドの終了、ソケットの破棄 // parameter // なし // return // なし //============================================== void Stop() { if (m_pCSendRecvThread != NULL) { fprintf(stderr, “Stop()\n”); m_pCSendRecvThread->End(); m_pCSendRecvThread->WaitForEnd(); SAFE_DELETE(m_pCSendRecvThread) DestroySocket(m_pConInfo->fdClient); SAFE_FREE(m_pConInfo->ai_addr) // ★ SAFE_FREE(m_pConInfo) } } //============================================== // function // ★メニューの表示 // parameter // なし // return // なし //============================================== void DispMenu() { Locate(1, 8, 2); fprintf(stderr, “%c:quit %c:メッセージ送信 : “, CMD_QUIT_CHAR, CMD_SEND_MSG_CHAR); } //============================================== // function // ★キーボード入力(改行まで)の取得 // 入力がないときはすぐにリターン // parameter // LPSTR pszString [in/out]データ格納用バッファ // int iSize [in]バッファのサイズ // return // 0:入力なし CMD_QUIT_CHAR:終了 CMD_SEND_MSG_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; default: break; } } return(iRet); } //============================================== // 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->ai_addr) // ★ SAFE_FREE(m_pConInfo) fRet = TRUE; } } return(fRet); } // 変更なし BOOL ShowInterfaceIndex(addrinfo *pAddr) // 変更なし BOOL SetHopLimit(SOCKET fd, int iFamily, int iLimit) //============================================== // function // ★メッセージパケットの送信 // parameter // char *pcData [in]UTF-8データ(NULLターミネートなし) // int iSize [in]データサイズ // retun // なし //============================================== BOOL SendMessagePacket(char *pcData, int iSize) { MsgDataRec *pMsgData = NULL; int iPacketSize = sizeof(HeaderRec) + iSize; // パケットサイズ BOOL fRet = FALSE; pMsgData = (MsgDataRec *)calloc(iPacketSize, sizeof(BYTE)); // パケット全体のエリアを確保 memcpy(pMsgData->header.bMagicData, MAGIC_STRING, strlen(MAGIC_STRING)); pMsgData->header.wCommand = htons(CMD_MSG_DATA); pMsgData->header.wDataLen = htons(iSize); memcpy(pMsgData->bMsgData, pcData, iSize); fRet = m_pCSendRecvThread->SetSendData((char *)pMsgData, iPacketSize); SAFE_FREE(pMsgData) return(fRet); }
CSendRecvThreadを変更します。
・ファイル送信関係機能を削除
・TCPをUDPに変更
これらの変更を行った後のSendRecvThread.hは次のようになります。
変更部分には★印を記しています。
【SendRecvThread.h】 #pragma once #include “ThreadJob.h” #include “define.h” // RecvMessagePacketの引数のため class CMySyncObject; // このクラスの使用することを宣言 class CRingBuff; // CRingBuffを使用するため #define SEND_BUFF_SIZE (1024 * 64) // 送信リングバッファのサイズ #if 1 #define SENDBUFSIZE (1024 + sizeof(HeaderRec)) // 一度に送信するサイズの最大値(共通ヘッダ分を加えてあります) #else #define SENDBUFSIZE (1024 * 4) // TCPで送信速度を1Mbpsより上げたいときは断片化処理をTCPのスタックに任せる // スタック(sendto)に渡すサイズを増やす #endif #define RECV_BUFF_SIZE (1024 * 64) // 受信リングバッファのサイズ // ★UDPは受信パケットサイズより小さなバッファでrecvfromを呼ぶと残りが破棄されるのでこのサイズには注意 // sendtoで送信されるサイズより小さくしない #define RCVBUFSIZE (1024 * 4) // ★一度に読む最大受信サイズ typedef struct { SOCKET fdClient; CMySyncObject *pCMySyncObject; // 同期オブジェクト sockaddr *ai_addr; // ★送信先アドレス情報(sendtoのパラメータ) size_t ai_addrlen; }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; // ゾンビ状態かどうかを保持 CRingBuff *m_pCRingBuffSend; // 送信データ格納用リングバッファ CRingBuff *m_pCRingBuffRecv; // 受信データ格納用リングバッファ 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); // 送信して良い時刻になったか };
SendRecvThread.cppの変更点はTCP(send, recv)をUDP(sendto, recvfrom)に変更します。
recvfromは、受信バッファ(スタック)にあるパケットより小さなバッファサイズで取得に
行くとバッファに入らないパケットの残りは破棄されてしまうので注意が必要です。
また、TCPの時に使用したファイル送信機能, 機能をスレッド化した箇所については削除
しています。
RecvFileSendResPacket, SendMessagePacket, SendFile, SendFileAbort, KillZombei
変更点には★を記しています。
【SendRecvThread.cpp】 #include “SendRecvThread.h” #include “MySyncObject.h” // CMySyncObjectを使うため #include “RingBuff.h” // CRingBuffを使うため // 送信速度 #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; // } //============================================== // function // ★デストラクタ // 不要なものを削除 // parameter // なし // return // なし //============================================== CSendRecvThread::~CSendRecvThread() { SAFE_DELETE(m_pCRingBuffSend) // 送信リングバッファの破棄 SAFE_DELETE(m_pCRingBuffRecv) // 受信リングバッファの破棄 } //============================================== // function // 機能を記述した関数 // 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; // 送信チェックした時刻を覚えるため sockaddr_storage PeerAddr; // ★送信元アドレス情報 socklen_t iPeerLen; // ★送信元アドレス情報のサイズ char szPeerAddr[NI_MAXHOST]; // ★相手アドレス,ポート番号をgetnameinfoで取得 char szServiceNo[NI_MAXSERV]; // ★ while (!m_fStopFlag) { 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と同等 // 受信処理 if (FD_ISSET(m_pConInfo->fdClient, &rfds)) // 受信データがあればrecvfrom実施 { // 受信リングバッファに空きがあれば取得する // ★UDPは受信パケットサイズより小さなバッファでrecvfromを呼ぶと残りが破棄されるので // 空きが受信パケットサイズより大きいときにrecvfromする if(m_pCRingBuffRecv->GetWriteableSize() >= RCVBUFSIZE) { iPeerLen = sizeof(PeerAddr); // ★送信元アドレス情報を格納する構造体のサイズ // ★rescfromは0バイトの受信はありうる if ((iRecvSize = recvfrom(m_pConInfo->fdClient, szRecvBuffer, RCVBUFSIZE, 0, (sockaddr *)&PeerAddr, &iPeerLen)) < 0) { // ★エラー時も他からの受信も処理するので切断しない m_pConInfo->pCMySyncObject->Lock(); DispErrorMsg(“Err:recvfrom”); m_pConInfo->pCMySyncObject->UnLock(); fRet = FALSE; break; } else { // ★送信元の表示 getnameinfo((sockaddr *)&PeerAddr, iPeerLen, szPeerAddr, NI_MAXHOST, szServiceNo, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV); fprintf(stderr, “recv from Addr:%s Port:%s\n:”, szPeerAddr, szServiceNo); // 取得したデータすぐに受信リングバッファに書きこむ // 受信リングバッファに書き込むのはこのスレッドだけなので、すべて書き込めるはず 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; } // 送信処理 if (FD_ISSET(m_pConInfo->fdClient, &wfds)) // ★送信可能ならsendto実施 { // 送信したいデータの取得(PATH_MTUより小さくなるように取得する) iSendSize = GetSendData(&pcData); if (sendto(m_pConInfo->fdClient, pcData, iSendSize, 0, m_pConInfo->ai_addr, (int)m_pConInfo->ai_addrlen) != iSendSize) { DispErrorMsg(“Err:sendto\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); } // 変更なし BOOL CSendRecvThread::SetSendData(char *pcData, int iSize) ———————————————- int CSendRecvThread::GetSendData(char **ppcData) // 変更なし BOOL CSendRecvThread::IsZombie() //============================================== // 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); } // 変更なし DWORD CSendRecvThread::CalcNextSendInterval(int iSentSize) // 変更なし BOOL CSendRecvThread::CanSendNow(DWORD dwNow)
Linux版も同様の対応で作成することができます。
以下はプロジェクトのリンクです。
UDPClient for Windows
UDPClient for Linux
動かしてみましょう
どちらもクライアントですが、送信元ポートを指定することでサーバのように動かす
ことができます。
送信先アドレスによってIPv6/IPv4のどちらで動作するかが決まります。
サーバ的動作側
送信先アドレスはIPv6/IPv4の指定に使う: ::1や127.0.0.1を指定
送信先ポートは使用しない: 適当な値を使用
送信元ポートは受信に使用する: クライアント側でこの値を指定
クライアント側
送信先アドレスはサーバ的動作側のアドレスを指定
送信先ポートはサーバ的動作側の受信に使用するポートを指定
送信元ポートはしてしなくてよい
クライアント側からメッセージ「aaaaaaaaaa」を送信します。
【サーバ的動作側】 [IPv6で動作させる] ./UDPClient ::1 12345 10000 Index:0 HopLimitOrg 64 HopLimitNew 10 q:quit m:メッセージ送信 :recv from Addr:fe80::7985:ee4e:75:1c54%wlan0 Port:54263 :CMD:100 Msg:recv aaaaaaaaaa [IPv4で動作させる] ./UDPClient 127.0.0.1 12345 10000 Index:0 HopLimitOrg 64 HopLimitNew 10 q:quit m:メッセージ送信 : recv from Addr:192.168.101.68 Port:56632 :CMD:100 Msg:recv aaaaaaaaa
【クライアント側】 [IPv6で動作させる] >UDPClient.exe fe80::11a0:ecd4:2cf8:6e5c 10000 Index:0 HopLimitOrg 128 HopLimitNew 10 q:quit m:メッセージ送信 : m aaaaaaaaaa [IPv4で動作させる] >UDPClient.exe 192.168.101.55 10000 Index:0 TTL 128 TTLNew 10 q:quit m:メッセージ送信 : m aaaaaaaaaa
(*)送信先アドレスでIPv6/IPv4のどちらで動作するかが決まりますので、サーバ的動作側と
クライアント側でファミリが一致していないと正しく通信できません。
クライアント側でも送信元ポートを入力し、サーバ的動作側でも送信先アドレス、
送信先ポートをクライアント側で指定したものを入力すると、双方向にメッセージを
送受信できます。
送信元ポート番号が動的なものから指定したものに変わります。
[IPv6で動作させる] ./UDPClient fe80::7985:ee4e:75:1c54 12345 10000 Index:0 HopLimitOrg 64 HopLimitNew 10 q:quit m:メッセージ送信 :recv from Addr:fe80::7985:ee4e:75:1c54%wlan0 Port:12345 :CMD:100 Msg:recv aaaaaaaaa q:quit m:メッセージ送信 : m bbbbbbbbbb
[IPv6で動作させる] >UDPClient.exe fe80::11a0:ecd4:2cf8:6e5c 10000 12345 Index:0 HopLimitOrg 128 HopLimitNew 10 q:quit m:メッセージ送信 : m aaaaaaaaaa q:quit m:メッセージ送信 : recv from Addr:fe80::11a0:ecd4:2cf8:6e5c%7 Port:10000 :CMD:100 Msg:recv bbbbbbbbbbb
クライアント側がIPv6でもIPv4でも、データ送受信ができる(UDPServer)には
どうすれば良いかは次回行うことにしましょう。