/*-------------------------------------------------------------------------- Project: HorizonSDK File: rdt_Transceiver.cpp Copyright 2009 Nintendo. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Date:: 2010-07-28#$ $Rev: 21987 $ $Author: hiratsu_daisuke $ *-------------------------------------------------------------------------*/ #include "stdafx.h" #include "rdt_Transceiver.h" #include #include "Test.h" #include "rdt_Segment.h" #include "rdt_Stopwatch.h" #include "rdt_Utility.h" namespace { #ifdef _WIN32 /*! @brief (信頼性のない通信路を経由して)データを送信します。生データの送信に使う、内部実装です。 pBufで指定されるデータには何も付け足したり、間引かれたりせずに送受信されます。 */ int SendRawData(SOCKET &sock, const void *pBuf, size_t bufSize) { ASSERT(pBuf!=NULL); ASSERT(bufSize > 0); const int c_flags = 0; int ret = ::send(sock, static_cast(pBuf), bufSize, c_flags); if(ret < 1) { int err = WSAGetLastError(); VERBOSE("::send()がエラーを返しました。(%d)\n", err); if(err==WSAEWOULDBLOCK) { // Resource temporarily unavailable VERBOSE("エラーコードはWSAEWOULDBLOCKだったので、続行します。\n"); return 0; } else if(err==WSAECONNRESET) { // Connection reset by peer. VERBOSE("エラーコードはWSAECONNRESETでした。続行します。\n"); return 0; } else if(err==WSAECONNABORTED) { // Software caused connection abort. VERBOSE("エラーコードはWSAECONNABORTEDでした。続行します。\n"); return 0; } else { PANIC("Stop. err = %d\n", err); return -1; } } else { return ret; } } /*! @brief データを受信します。生データの受信に使う、内部実装です。 pBufで指定されるバッファには、何も付け足したり、間引かれたりしていない データが書き込まれます。 */ int RecvRawData(SOCKET &sock, void *pBuf, size_t bufSize) { int ret = ::recv(sock, static_cast(pBuf), bufSize, 0); if(ret < 0) { int err = WSAGetLastError(); switch(err) { case WSAEWOULDBLOCK: // 単にデータが無かっただけ。 return 0; case WSAECONNRESET: LOG("リモートのコネクションが消失したので、データを取得できません。\n"); return -1; case WSAECONNABORTED: LOG("ホスト側でコネクションが中断(アボート)されました。\n"); return -1; default: LOG("::recv()が未知のエラーを返しました。(%d)\n", err); return -1; } } else { return ret; } } #elif defined(NN_PLATFORM_CTR) // Windows版と異なり、udsのエラーを取得するための引数を追加している。 int SendRawData(nn::uds::EndpointDescriptor *pSendEp, u16 nodeId, u8 port, const void *pBuf, size_t bufSize, nn::Result *pResult) { using namespace nn::uds; ASSERT(pSendEp!=NULL); ASSERT(pBuf!=NULL); ASSERT(bufSize > 0); ASSERT(bufSize <= UDS_PACKET_PAYLOAD_MAX_SIZE); // UDS パケットの最大ペイロードサイズを越えない ASSERT(pResult!=NULL); const bit8 c_option = NO_WAIT; // ストップウォッチで計測。 static nn::rdt::CTR::detail::Stopwatch s_sw("uds::SendTo()"); s_sw.Start(); *pResult = SendTo(*pSendEp, pBuf, bufSize, nodeId, port, c_option); s_sw.Stop(); if(pResult->IsSuccess()) { VERBOSE("Sent data.\n"); return bufSize; } else { nn::rdt::CTR::PrintResultCode(*pResult); LOG("SendTo() Failed.\n"); return -1; } } // Windows版と異なり、udsのエラーを取得するための引数を追加している。 int RecvRawData(nn::uds::EndpointDescriptor *pRecvEp, void *pBuf, size_t bufSize, nn::Result *pResult) { using namespace nn::uds; ASSERT(pRecvEp!=NULL); ASSERT(pBuf!=NULL); ASSERT(bufSize > 0); ASSERT(bufSize <= UDS_PACKET_PAYLOAD_MAX_SIZE); // UDS パケットの最大ペイロードサイズを越えない ASSERT(pResult!=NULL); u16 srcNodeId = 0x0000; size_t received = 0; const bit8 opt = nn::uds::NO_WAIT; // ストップウォッチで計測。 static nn::rdt::CTR::detail::Stopwatch s_sw("uds::ReceiveFrom()"); s_sw.Start(); *pResult = ReceiveFrom(*pRecvEp, pBuf, &received, &srcNodeId, bufSize, opt); s_sw.Stop(); if(pResult->IsSuccess()) { VERBOSE("Received. Src Node: %u\n", srcNodeId); return received; } else { if(pResult->GetSummary()==nn::Result::SUMMARY_NOT_FOUND) { VERBOSE("It seems that data is not arrived. It is not an error.\n"); return 0; } else { nn::rdt::CTR::PrintResultCode(*pResult); LOG("ReceiveFrom() failed.\n"); return -1; } } } #endif // end of _WIN32 #ifdef _WIN32 u16 port = 0; nn::rdt::CTR::Segment s_sendSeg; nn::rdt::CTR::Segment s_recvSeg; const char *c_msg = "Transceiver Unit Test\n"; // 単体テスト用。 void serverSide(LPVOID pParam) { using namespace nn::rdt::CTR; SOCKET sock = SetupServerSide(port); // データが届くのを待つ SleepCurrentThread(1000); Transceiver t; nn::Result result = t.Initialize(sock); CU_ASSERT(result.IsSuccess()); result = t.Pull(&s_recvSeg); CU_ASSERT(result.IsSuccess()); LOG("受信したセグメントの内容は、\n"); s_recvSeg.PrintDebugInfo(); LOG("サーバー側のソケットの後始末をします。\n"); CleanupServerSide(sock); } // 単体テスト用。 static void clientSide(LPVOID pParam) { using namespace nn::rdt::CTR; SOCKET sock = SetupClientSide(port); Transceiver t; nn::Result result = t.Initialize(sock); CU_ASSERT(result.IsSuccess()); s_sendSeg.SetData(c_msg, strlen(c_msg)); t.Put(s_sendSeg); // セグメント送信 // TCPセッション終了 LOG("クライアント側のソケットの後始末をします。\n"); CleanupClientSide(sock); } #endif // end of _WIN32 } // end of anonymous namespace namespace nn { namespace rdt { namespace CTR { Transceiver::Transceiver(void) :m_initialized(false) { } Transceiver::~Transceiver(void) { Finalize(); } #ifdef _WIN32 nn::Result Transceiver::Initialize(SOCKET sock) { if(m_initialized) { return ResultAlreadyInitialized(); } else { m_sock = sock; m_initialized = true; return ResultSuccess(); } } #elif defined(NN_PLATFORM_CTR) nn::Result Transceiver::Initialize(u16 nodeId, u8 port) { if(m_initialized) { return ResultAlreadyInitialized(); } else { nn::Result result; result = nn::uds::CreateEndpoint(&m_sendEp); if(result.IsFailure()) { return result; } result = nn::uds::CreateEndpoint(&m_recvEp); if(result.IsFailure()) { nn::Result r = nn::uds::DestroyEndpoint(&m_sendEp); NN_UTIL_PANIC_IF_FAILED(r); return result; } result = nn::uds::Attach(&m_recvEp, nodeId, port); // socket における bind() + connect() に近い機能。Receive() の前に必須。 if(result.IsFailure()) { nn::Result r; r = nn::uds::DestroyEndpoint(&m_recvEp); NN_UTIL_PANIC_IF_FAILED(r); r = nn::uds::DestroyEndpoint(&m_sendEp); NN_UTIL_PANIC_IF_FAILED(r); return result; } m_remoteNodeId = nodeId; m_port = port; m_initialized = true; VERBOSE("Transceiver initialized.\n"); return ResultSuccess(); } } #else #error no platform selected #endif #ifdef _WIN32 void Transceiver::Finalize(void) { if(m_initialized) { m_initialized = false; } else { // Do nothing. } } #elif defined(NN_PLATFORM_CTR) void Transceiver::Finalize(void) { if(m_initialized) { m_initialized = false; nn::Result result; result = nn::uds::DestroyEndpoint(&m_recvEp); NN_UTIL_PANIC_IF_FAILED(result); result = nn::uds::DestroyEndpoint(&m_sendEp); NN_UTIL_PANIC_IF_FAILED(result); } else { // Do nothing. } } #else #error no platform selected #endif // セグメントの送信を行います nn::Result Transceiver::Put(const Segment &seg) { ASSERT(m_initialized); #ifdef _WIN32 int n = SendRawData(m_sock, &seg, sizeof(Segment)); #else nn::Result ret; int n = SendRawData(&m_sendEp, m_remoteNodeId, m_port, &seg, sizeof(Segment), &ret); #endif if(n==sizeof(Segment)) { // たぶん送信できたであろう、という状況。 // 実際にリモートに届いたかどうかは別問題。 VERBOSE("セグメントの送信は、たぶんうまくいきました。\n"); return ResultSuccess(); } else { VERBOSE("セグメントの送信を試みましたが、SendRawData()に失敗したようです…。(n = %d)\n", n); #ifdef _WIN32 return MakeDisconnected(); // Windows環境ではUDSが無いので、しかたなくMakeDisconnected()でお茶を濁す #else return ret; // UDSのエラーをそのまま返す #endif } } // セグメントを受信します。 nn::Result Transceiver::Pull(Segment *pSeg) { ASSERT(m_initialized); ASSERT(pSeg!=NULL); #ifdef _WIN32 int n = RecvRawData(m_sock, pSeg, sizeof(Segment)); #else nn::Result ret; int n = RecvRawData(&m_recvEp, pSeg, sizeof(Segment), &ret); #endif if((n==sizeof(Segment)) && pSeg->IsValid()) { // LOG("受信セグメント:\n"); // pSeg->PrintDebugInfo(); return ResultSuccess(); } else if(n==0) { // データがまだ到着していないだけ。スルー。 return ResultNoData(); } else { // コネクション断絶の疑い。 LOG("It seems that size of segment is wrong(%d byte)\n", n); #ifdef _WIN32 return MakeDisconnected(); // Windows環境ではUDSが無いので、しかたなくMakeDisconnected()でお茶を濁す #else return ret; // UDSのエラーをそのまま返す #endif } } #ifdef _WIN32 void Transceiver::Test(void) { port = GetAvailablePort(); // サーバーとクライアントのスレッドを作成 HANDLE hThread[2]; hThread[0] = (HANDLE)_beginthread(serverSide, 0, NULL); hThread[1] = (HANDLE)_beginthread(clientSide, 0, NULL); // スレッド終了待ち WaitForMultipleObjects(2, hThread, TRUE, INFINITE); char buf[Segment::PAYLOAD_SIZE]; u32 sz = s_recvSeg.GetData(buf, Segment::PAYLOAD_SIZE); buf[sz] = '\0'; CU_ASSERT(strcmp(c_msg, buf)==0); } #endif /* memo WSAECONNRESET 10054 Connection reset by peer. An existing connection was forcibly closed by the remote host. This normally results if the peer application on the remote host is suddenly stopped, the host is rebooted, the host or remote network interface is disabled, or the remote host uses a hard close (see setsockopt for more information on the SO_LINGER option on the remote socket). This error may also result if a connection was broken due to keep-alive activity detecting a failure while one or more operations are in progress. Operations that were in progress fail with WSAENETRESET. Subsequent operations fail with WSAECONNRESET. WSAECONNABORTED 10053 Software caused connection abort. An established connection was aborted by the software in your host computer, possibly due to a data transmission time-out or protocol error. */ }}} // namespace nn::rdt::CTR