You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
645 lines
14 KiB
645 lines
14 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
or by snail mail at: |
|
Cyan Worlds, Inc. |
|
14617 N Newport Hwy |
|
Mead, WA 99021 |
|
|
|
*==LICENSE==*/ |
|
/***************************************************************************** |
|
* |
|
* $/Plasma20/Sources/Plasma/NucleusLib/pnSimpleNet/pnSimpleNet.cpp |
|
* |
|
***/ |
|
|
|
#include "Pch.h" |
|
#pragma hdrstop |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Local types |
|
* |
|
***/ |
|
|
|
struct SimpleNetConn : AtomicRef { |
|
LINK(SimpleNetConn) link; |
|
AsyncSocket sock; |
|
AsyncCancelId cancelId; |
|
unsigned channelId; |
|
bool abandoned; |
|
struct ConnectParam * connectParam; |
|
|
|
SimpleNet_MsgHeader * oversizeMsg; |
|
ARRAY(byte) oversizeBuffer; |
|
|
|
~SimpleNetConn () { |
|
ASSERT(!link.IsLinked()); |
|
} |
|
}; |
|
|
|
struct SimpleNetChannel : AtomicRef, THashKeyVal<unsigned> { |
|
HASHLINK(SimpleNetChannel) link; |
|
|
|
FSimpleNetOnMsg onMsg; |
|
FSimpleNetOnError onError; |
|
|
|
LISTDECL(SimpleNetConn, link) conns; |
|
|
|
SimpleNetChannel (unsigned channel) |
|
: THashKeyVal<unsigned>(channel) |
|
{ } |
|
~SimpleNetChannel () { |
|
ASSERT(!link.IsLinked()); |
|
ASSERT(!conns.Head()); |
|
} |
|
}; |
|
|
|
struct ConnectParam { |
|
SimpleNetChannel * channel; |
|
FSimpleNetOnConnect callback; |
|
void * param; |
|
|
|
~ConnectParam () { |
|
if (channel) |
|
channel->DecRef(); |
|
} |
|
}; |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Local data |
|
* |
|
***/ |
|
|
|
static bool s_running; |
|
static CCritSect s_critsect; |
|
static FSimpleNetQueryAccept s_queryAccept; |
|
static void * s_queryAcceptParam; |
|
|
|
static HASHTABLEDECL( |
|
SimpleNetChannel, |
|
THashKeyVal<unsigned>, |
|
link |
|
) s_channels; |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Local functions |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
static void NotifyConnSocketConnect (SimpleNetConn * conn) { |
|
|
|
conn->TransferRef("Connecting", "Connected"); |
|
|
|
conn->connectParam->callback( |
|
conn->connectParam->param, |
|
conn, |
|
kNetSuccess |
|
); |
|
|
|
DEL(conn->connectParam); |
|
conn->connectParam = nil; |
|
} |
|
|
|
//============================================================================ |
|
static void NotifyConnSocketConnectFailed (SimpleNetConn * conn) { |
|
|
|
s_critsect.Enter(); |
|
{ |
|
conn->link.Unlink(); |
|
} |
|
s_critsect.Leave(); |
|
|
|
conn->connectParam->callback( |
|
conn->connectParam->param, |
|
nil, |
|
kNetErrConnectFailed |
|
); |
|
|
|
DEL(conn->connectParam); |
|
conn->connectParam = nil; |
|
|
|
conn->DecRef("Connecting"); |
|
conn->DecRef("Lifetime"); |
|
} |
|
|
|
//============================================================================ |
|
static void NotifyConnSocketDisconnect (SimpleNetConn * conn) { |
|
|
|
bool abandoned; |
|
SimpleNetChannel * channel; |
|
s_critsect.Enter(); |
|
{ |
|
abandoned = conn->abandoned; |
|
if (nil != (channel = s_channels.Find(conn->channelId))) |
|
channel->IncRef(); |
|
conn->link.Unlink(); |
|
} |
|
s_critsect.Leave(); |
|
|
|
if (channel && !abandoned) { |
|
channel->onError(conn, kNetErrDisconnected); |
|
channel->DecRef(); |
|
} |
|
|
|
conn->DecRef("Connected"); |
|
} |
|
|
|
//============================================================================ |
|
static bool NotifyConnSocketRead (SimpleNetConn * conn, AsyncNotifySocketRead * read) { |
|
|
|
SimpleNetChannel * channel; |
|
s_critsect.Enter(); |
|
{ |
|
if (nil != (channel = s_channels.Find(conn->channelId))) |
|
channel->IncRef(); |
|
} |
|
s_critsect.Leave(); |
|
|
|
if (!channel) |
|
return false; |
|
|
|
bool result = true; |
|
|
|
const byte * curr = read->buffer; |
|
const byte * term = curr + read->bytes; |
|
|
|
while (curr < term) { |
|
// Reading oversize msg? |
|
if (conn->oversizeBuffer.Count()) { |
|
unsigned spaceLeft = conn->oversizeMsg->messageBytes - conn->oversizeBuffer.Count(); |
|
unsigned copyBytes = min(spaceLeft, term - curr); |
|
conn->oversizeBuffer.Add(curr, copyBytes); |
|
|
|
curr += copyBytes; |
|
|
|
// Wait until we have received the entire message |
|
if (copyBytes != spaceLeft) |
|
break; |
|
|
|
// Dispatch oversize msg |
|
if (!channel->onMsg(conn, conn->oversizeMsg)) { |
|
result = false; |
|
break; |
|
} |
|
|
|
conn->oversizeBuffer.SetCount(0); |
|
continue; |
|
} |
|
|
|
// Wait until we receive the entire message header |
|
if (term - curr < sizeof(SimpleNet_MsgHeader)) |
|
break; |
|
|
|
SimpleNet_MsgHeader * msg = (SimpleNet_MsgHeader *) read->buffer; |
|
|
|
// Sanity check message size |
|
if (msg->messageBytes < sizeof(*msg)) { |
|
result = false; |
|
break; |
|
} |
|
|
|
// Handle oversized messages |
|
if (msg->messageBytes > kAsyncSocketBufferSize) { |
|
|
|
conn->oversizeBuffer.SetCount(msg->messageBytes); |
|
conn->oversizeMsg = (SimpleNet_MsgHeader *) conn->oversizeBuffer.Ptr(); |
|
*conn->oversizeMsg = *msg; |
|
|
|
curr += sizeof(*msg); |
|
continue; |
|
} |
|
|
|
// Wait until we have received the entire message |
|
const byte * msgTerm = (const byte *) curr + msg->messageBytes; |
|
if (msgTerm > term) |
|
break; |
|
curr = msgTerm; |
|
|
|
// Dispatch msg |
|
if (!channel->onMsg(conn, msg)) { |
|
result = false; |
|
break; |
|
} |
|
} |
|
|
|
// Return count of bytes we processed |
|
read->bytesProcessed = curr - read->buffer; |
|
|
|
channel->DecRef(); |
|
return result; |
|
} |
|
|
|
//============================================================================ |
|
static bool AsyncNotifySocketProc ( |
|
AsyncSocket sock, |
|
EAsyncNotifySocket code, |
|
AsyncNotifySocket * notify, |
|
void ** userState |
|
) { |
|
bool result = true; |
|
SimpleNetConn * conn; |
|
|
|
switch (code) { |
|
case kNotifySocketListenSuccess: { |
|
|
|
AsyncNotifySocketListen * listen = (AsyncNotifySocketListen *) notify; |
|
|
|
const SimpleNet_ConnData & connect = *(const SimpleNet_ConnData *) listen->buffer; |
|
listen->bytesProcessed += sizeof(connect); |
|
|
|
SimpleNetChannel * channel; |
|
s_critsect.Enter(); |
|
{ |
|
if (nil != (channel = s_channels.Find(connect.channelId))) |
|
channel->IncRef(); |
|
} |
|
s_critsect.Leave(); |
|
|
|
if (!channel) |
|
break; |
|
|
|
conn = NEWZERO(SimpleNetConn); |
|
conn->channelId = channel->GetValue(); |
|
conn->IncRef("Lifetime"); |
|
conn->IncRef("Connected"); |
|
conn->sock = sock; |
|
*userState = conn; |
|
|
|
bool accepted = s_queryAccept( |
|
s_queryAcceptParam, |
|
channel->GetValue(), |
|
conn, |
|
listen->remoteAddr |
|
); |
|
|
|
if (!accepted) { |
|
SimpleNetDisconnect(conn); |
|
} |
|
else { |
|
s_critsect.Enter(); |
|
{ |
|
channel->conns.Link(conn); |
|
} |
|
s_critsect.Leave(); |
|
} |
|
|
|
channel->DecRef(); |
|
} |
|
break; |
|
|
|
case kNotifySocketConnectSuccess: { |
|
conn = (SimpleNetConn *) notify->param; |
|
*userState = conn; |
|
bool abandoned; |
|
|
|
s_critsect.Enter(); |
|
{ |
|
conn->sock = sock; |
|
conn->cancelId = 0; |
|
abandoned = conn->abandoned; |
|
} |
|
s_critsect.Leave(); |
|
|
|
if (abandoned) |
|
AsyncSocketDisconnect(sock, true); |
|
else |
|
NotifyConnSocketConnect(conn); |
|
} |
|
break; |
|
|
|
case kNotifySocketConnectFailed: |
|
conn = (SimpleNetConn *) notify->param; |
|
NotifyConnSocketConnectFailed(conn); |
|
break; |
|
|
|
case kNotifySocketDisconnect: |
|
conn = (SimpleNetConn *) *userState; |
|
NotifyConnSocketDisconnect(conn); |
|
break; |
|
|
|
case kNotifySocketRead: |
|
conn = (SimpleNetConn *) *userState; |
|
result = NotifyConnSocketRead(conn, (AsyncNotifySocketRead *) notify); |
|
break; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
//============================================================================ |
|
static void Connect (const NetAddress & addr, ConnectParam * cp) { |
|
|
|
SimpleNetConn * conn = NEWZERO(SimpleNetConn); |
|
conn->channelId = cp->channel->GetValue(); |
|
conn->connectParam = cp; |
|
conn->IncRef("Lifetime"); |
|
conn->IncRef("Connecting"); |
|
|
|
s_critsect.Enter(); |
|
{ |
|
cp->channel->conns.Link(conn); |
|
|
|
SimpleNet_Connect connect; |
|
connect.hdr.connType = kConnTypeSimpleNet; |
|
connect.hdr.hdrBytes = sizeof(connect.hdr); |
|
connect.hdr.buildId = BuildId(); |
|
connect.hdr.buildType = BuildType(); |
|
connect.hdr.branchId = BranchId(); |
|
connect.hdr.productId = ProductId(); |
|
connect.data.channelId = cp->channel->GetValue(); |
|
|
|
AsyncSocketConnect( |
|
&conn->cancelId, |
|
addr, |
|
AsyncNotifySocketProc, |
|
conn, |
|
&connect, |
|
sizeof(connect) |
|
); |
|
|
|
conn = nil; |
|
cp = nil; |
|
} |
|
s_critsect.Leave(); |
|
|
|
DEL(conn); |
|
DEL(cp); |
|
} |
|
|
|
//============================================================================ |
|
static void AsyncLookupCallback ( |
|
void * param, |
|
const wchar name[], |
|
unsigned addrCount, |
|
const NetAddress addrs[] |
|
) { |
|
ConnectParam * cp = (ConnectParam *)param; |
|
|
|
if (!addrCount) { |
|
if (cp->callback) |
|
cp->callback(cp->param, nil, kNetErrNameLookupFailed); |
|
DEL(cp); |
|
return; |
|
} |
|
|
|
Connect(addrs[0], (ConnectParam *)param); |
|
} |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Exported functions |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
void SimpleNetInitialize () { |
|
|
|
s_running = true; |
|
|
|
AsyncSocketRegisterNotifyProc( |
|
kConnTypeSimpleNet, |
|
AsyncNotifySocketProc |
|
); |
|
} |
|
|
|
//============================================================================ |
|
void SimpleNetShutdown () { |
|
|
|
s_running = false; |
|
|
|
ASSERT(!s_channels.Head()); |
|
|
|
AsyncSocketUnregisterNotifyProc( |
|
kConnTypeSimpleNet, |
|
AsyncNotifySocketProc |
|
); |
|
} |
|
|
|
//============================================================================ |
|
void SimpleNetConnIncRef (SimpleNetConn * conn) { |
|
|
|
ASSERT(s_running); |
|
ASSERT(conn); |
|
|
|
conn->IncRef(); |
|
} |
|
|
|
//============================================================================ |
|
void SimpleNetConnDecRef (SimpleNetConn * conn) { |
|
|
|
ASSERT(s_running); |
|
ASSERT(conn); |
|
|
|
conn->DecRef(); |
|
} |
|
|
|
//============================================================================ |
|
bool SimpleNetStartListening ( |
|
FSimpleNetQueryAccept queryAccept, |
|
void * param |
|
) { |
|
ASSERT(s_running); |
|
ASSERT(queryAccept); |
|
ASSERT(!s_queryAccept); |
|
|
|
s_queryAccept = queryAccept; |
|
s_queryAcceptParam = param; |
|
|
|
NetAddress addr; |
|
NetAddressFromNode(0, kNetDefaultSimpleNetPort, &addr); |
|
return (0 != AsyncSocketStartListening(addr, nil)); |
|
} |
|
|
|
//============================================================================ |
|
void SimpleNetStopListening () { |
|
|
|
ASSERT(s_running); |
|
|
|
NetAddress addr; |
|
NetAddressFromNode(0, kNetDefaultSimpleNetPort, &addr); |
|
AsyncSocketStopListening(addr, nil); |
|
|
|
s_queryAccept = nil; |
|
s_queryAcceptParam = nil; |
|
} |
|
|
|
//============================================================================ |
|
void SimpleNetCreateChannel ( |
|
unsigned channelId, |
|
FSimpleNetOnMsg onMsg, |
|
FSimpleNetOnError onError |
|
) { |
|
ASSERT(s_running); |
|
|
|
SimpleNetChannel * channel = NEWZERO(SimpleNetChannel)(channelId); |
|
channel->IncRef(); |
|
|
|
s_critsect.Enter(); |
|
{ |
|
#ifdef HS_DEBUGGING |
|
{ |
|
SimpleNetChannel * existing = s_channels.Find(channelId); |
|
ASSERT(!existing); |
|
} |
|
#endif |
|
|
|
channel->onMsg = onMsg; |
|
channel->onError = onError; |
|
s_channels.Add(channel); |
|
channel->IncRef(); |
|
} |
|
s_critsect.Leave(); |
|
|
|
channel->DecRef(); |
|
} |
|
|
|
//============================================================================ |
|
void SimpleNetDestroyChannel (unsigned channelId) { |
|
|
|
ASSERT(s_running); |
|
|
|
SimpleNetChannel * channel; |
|
s_critsect.Enter(); |
|
{ |
|
if (nil != (channel = s_channels.Find(channelId))) { |
|
s_channels.Unlink(channel); |
|
while (SimpleNetConn * conn = channel->conns.Head()) { |
|
SimpleNetDisconnect(conn); |
|
channel->conns.Unlink(conn); |
|
} |
|
} |
|
} |
|
s_critsect.Leave(); |
|
|
|
if (channel) |
|
channel->DecRef(); |
|
} |
|
|
|
//============================================================================ |
|
void SimpleNetStartConnecting ( |
|
unsigned channelId, |
|
const wchar addr[], |
|
FSimpleNetOnConnect onConnect, |
|
void * param |
|
) { |
|
ASSERT(s_running); |
|
ASSERT(onConnect); |
|
|
|
ConnectParam * cp = NEW(ConnectParam); |
|
cp->callback = onConnect; |
|
cp->param = param; |
|
|
|
s_critsect.Enter(); |
|
{ |
|
if (nil != (cp->channel = s_channels.Find(channelId))) |
|
cp->channel->IncRef(); |
|
} |
|
s_critsect.Leave(); |
|
|
|
ASSERT(cp->channel); |
|
|
|
// Do we need to lookup the address? |
|
const wchar * name = addr; |
|
while (unsigned ch = *name) { |
|
++name; |
|
if (!(isdigit(ch) || ch == L'.' || ch == L':')) { |
|
|
|
AsyncCancelId cancelId; |
|
AsyncAddressLookupName( |
|
&cancelId, |
|
AsyncLookupCallback, |
|
addr, |
|
kNetDefaultSimpleNetPort, |
|
cp |
|
); |
|
break; |
|
} |
|
} |
|
if (!name[0]) { |
|
NetAddress netAddr; |
|
NetAddressFromString(&netAddr, addr, kNetDefaultSimpleNetPort); |
|
Connect(netAddr, cp); |
|
} |
|
} |
|
|
|
//============================================================================ |
|
void SimpleNetDisconnect ( |
|
SimpleNetConn * conn |
|
) { |
|
ASSERT(s_running); |
|
ASSERT(conn); |
|
|
|
s_critsect.Enter(); |
|
{ |
|
conn->abandoned = true; |
|
if (conn->sock) { |
|
AsyncSocketDisconnect(conn->sock, true); |
|
conn->sock = nil; |
|
} |
|
else if (conn->cancelId) { |
|
AsyncSocketConnectCancel(AsyncNotifySocketProc, conn->cancelId); |
|
conn->cancelId = nil; |
|
} |
|
} |
|
s_critsect.Leave(); |
|
|
|
conn->DecRef("Lifetime"); |
|
} |
|
|
|
//============================================================================ |
|
void SimpleNetSend ( |
|
SimpleNetConn * conn, |
|
SimpleNet_MsgHeader * msg |
|
) { |
|
ASSERT(s_running); |
|
ASSERT(msg); |
|
ASSERT(msg->messageBytes != (dword)-1); |
|
ASSERT(conn); |
|
|
|
s_critsect.Enter(); |
|
{ |
|
if (conn->sock) |
|
AsyncSocketSend(conn->sock, msg, msg->messageBytes); |
|
} |
|
s_critsect.Leave(); |
|
}
|
|
|