Changeset View
Changeset View
Standalone View
Standalone View
source/network/NetServer.cpp
Show First 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | |||||
/** | /** | ||||
* enet_host_service timeout (msecs). | * enet_host_service timeout (msecs). | ||||
* Smaller numbers may hurt performance; larger numbers will | * Smaller numbers may hurt performance; larger numbers will | ||||
* hurt latency responding to messages from game thread. | * hurt latency responding to messages from game thread. | ||||
*/ | */ | ||||
static const int HOST_SERVICE_TIMEOUT = 50; | static const int HOST_SERVICE_TIMEOUT = 50; | ||||
CNetServer* g_NetServer = NULL; | CNetServer* g_NetServer = nullptr; | ||||
static CStr DebugName(CNetServerSession* session) | static CStr DebugName(CNetServerSession* session) | ||||
{ | { | ||||
if (session == NULL) | if (session == nullptr) | ||||
return "[unknown host]"; | return "[unknown host]"; | ||||
if (session->GetGUID().empty()) | if (session->GetGUID().empty()) | ||||
return "[unauthed host]"; | return "[unauthed host]"; | ||||
return "[" + session->GetGUID().substr(0, 8) + "...]"; | return "[" + session->GetGUID().substr(0, 8) + "...]"; | ||||
} | } | ||||
/** | /** | ||||
* Async task for receiving the initial game state to be forwarded to another | * Async task for receiving the initial game state to be forwarded to another | ||||
Show All 9 Lines | public: | ||||
} | } | ||||
virtual void OnComplete() | virtual void OnComplete() | ||||
{ | { | ||||
// We've received the game state from an existing player - now | // We've received the game state from an existing player - now | ||||
// we need to send it onwards to the newly rejoining player | // we need to send it onwards to the newly rejoining player | ||||
// Find the session corresponding to the rejoining host (if any) | // Find the session corresponding to the rejoining host (if any) | ||||
CNetServerSession* session = NULL; | CNetServerSession* session = nullptr; | ||||
for (CNetServerSession* serverSession : m_Server.m_Sessions) | for (CNetServerSession* serverSession : m_Server.m_Sessions) | ||||
{ | { | ||||
if (serverSession->GetHostID() == m_RejoinerHostID) | if (serverSession->GetHostID() == m_RejoinerHostID) | ||||
{ | { | ||||
session = serverSession; | session = serverSession; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
Show All 22 Lines | |||||
* XXX: We use some non-threadsafe functions from the worker thread. | * XXX: We use some non-threadsafe functions from the worker thread. | ||||
* See http://trac.wildfiregames.com/ticket/654 | * See http://trac.wildfiregames.com/ticket/654 | ||||
*/ | */ | ||||
CNetServerWorker::CNetServerWorker(bool useLobbyAuth, int autostartPlayers) : | CNetServerWorker::CNetServerWorker(bool useLobbyAuth, int autostartPlayers) : | ||||
m_AutostartPlayers(autostartPlayers), | m_AutostartPlayers(autostartPlayers), | ||||
m_LobbyAuth(useLobbyAuth), | m_LobbyAuth(useLobbyAuth), | ||||
m_Shutdown(false), | m_Shutdown(false), | ||||
m_ScriptInterface(NULL), | m_ScriptInterface(nullptr), | ||||
m_NextHostID(1), m_Host(NULL), m_HostGUID(), m_Stats(NULL), | m_NextHostID(1), m_Host(nullptr), m_HostGUID(), m_Stats(nullptr), | ||||
m_LastConnectionCheck(0) | m_LastConnectionCheck(0) | ||||
{ | { | ||||
m_State = SERVER_STATE_UNCONNECTED; | m_State = SERVER_STATE_UNCONNECTED; | ||||
m_ServerTurnManager = NULL; | m_ServerTurnManager = nullptr; | ||||
m_ServerName = DEFAULT_SERVER_NAME; | m_ServerName = DEFAULT_SERVER_NAME; | ||||
} | } | ||||
CNetServerWorker::~CNetServerWorker() | CNetServerWorker::~CNetServerWorker() | ||||
{ | { | ||||
if (m_State != SERVER_STATE_UNCONNECTED) | if (m_State != SERVER_STATE_UNCONNECTED) | ||||
{ | { | ||||
// Tell the thread to shut down | // Tell the thread to shut down | ||||
{ | { | ||||
CScopeLock lock(m_WorkerMutex); | CScopeLock lock(m_WorkerMutex); | ||||
m_Shutdown = true; | m_Shutdown = true; | ||||
} | } | ||||
// Wait for it to shut down cleanly | // Wait for it to shut down cleanly | ||||
pthread_join(m_WorkerThread, NULL); | pthread_join(m_WorkerThread, nullptr); | ||||
} | } | ||||
// Clean up resources | // Clean up resources | ||||
delete m_Stats; | delete m_Stats; | ||||
for (CNetServerSession* session : m_Sessions) | for (CNetServerSession* session : m_Sessions) | ||||
{ | { | ||||
Show All 27 Lines | bool CNetServerWorker::SetupConnection(const u16 port) | ||||
m_Stats = new CNetStatsTable(); | m_Stats = new CNetStatsTable(); | ||||
if (CProfileViewer::IsInitialised()) | if (CProfileViewer::IsInitialised()) | ||||
g_ProfileViewer.AddRootTable(m_Stats); | g_ProfileViewer.AddRootTable(m_Stats); | ||||
m_State = SERVER_STATE_PREGAME; | m_State = SERVER_STATE_PREGAME; | ||||
// Launch the worker thread | // Launch the worker thread | ||||
int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this); | int ret = pthread_create(&m_WorkerThread, nullptr, &RunThread, this); | ||||
ENSURE(ret == 0); | ENSURE(ret == 0); | ||||
#if CONFIG2_MINIUPNPC | #if CONFIG2_MINIUPNPC | ||||
// Launch the UPnP thread | // Launch the UPnP thread | ||||
ret = pthread_create(&m_UPnPThread, NULL, &SetupUPnP, NULL); | ret = pthread_create(&m_UPnPThread, nullptr, &SetupUPnP, nullptr); | ||||
ENSURE(ret == 0); | ENSURE(ret == 0); | ||||
#endif | #endif | ||||
return true; | return true; | ||||
} | } | ||||
#if CONFIG2_MINIUPNPC | #if CONFIG2_MINIUPNPC | ||||
void* CNetServerWorker::SetupUPnP(void*) | void* CNetServerWorker::SetupUPnP(void*) | ||||
{ | { | ||||
// Values we want to set. | // Values we want to set. | ||||
char psPort[6]; | char psPort[6]; | ||||
sprintf_s(psPort, ARRAY_SIZE(psPort), "%d", PS_DEFAULT_PORT); | sprintf_s(psPort, ARRAY_SIZE(psPort), "%d", PS_DEFAULT_PORT); | ||||
const char* leaseDuration = "0"; // Indefinite/permanent lease duration. | const char* leaseDuration = "0"; // Indefinite/permanent lease duration. | ||||
const char* description = "0AD Multiplayer"; | const char* description = "0AD Multiplayer"; | ||||
const char* protocall = "UDP"; | const char* protocall = "UDP"; | ||||
char internalIPAddress[64]; | char internalIPAddress[64]; | ||||
char externalIPAddress[40]; | char externalIPAddress[40]; | ||||
// Variables to hold the values that actually get set. | // Variables to hold the values that actually get set. | ||||
char intClient[40]; | char intClient[40]; | ||||
char intPort[6]; | char intPort[6]; | ||||
char duration[16]; | char duration[16]; | ||||
// Intermediate variables. | // Intermediate variables. | ||||
struct UPNPUrls urls; | struct UPNPUrls urls; | ||||
struct IGDdatas data; | struct IGDdatas data; | ||||
struct UPNPDev* devlist = NULL; | struct UPNPDev* devlist = nullptr; | ||||
// Cached root descriptor URL. | // Cached root descriptor URL. | ||||
std::string rootDescURL; | std::string rootDescURL; | ||||
CFG_GET_VAL("network.upnprootdescurl", rootDescURL); | CFG_GET_VAL("network.upnprootdescurl", rootDescURL); | ||||
if (!rootDescURL.empty()) | if (!rootDescURL.empty()) | ||||
LOGMESSAGE("Net server: attempting to use cached root descriptor URL: %s", rootDescURL.c_str()); | LOGMESSAGE("Net server: attempting to use cached root descriptor URL: %s", rootDescURL.c_str()); | ||||
int ret = 0; | int ret = 0; | ||||
bool allocatedUrls = false; | bool allocatedUrls = false; | ||||
// Try a cached URL first | // Try a cached URL first | ||||
if (!rootDescURL.empty() && UPNP_GetIGDFromUrl(rootDescURL.c_str(), &urls, &data, internalIPAddress, sizeof(internalIPAddress))) | if (!rootDescURL.empty() && UPNP_GetIGDFromUrl(rootDescURL.c_str(), &urls, &data, internalIPAddress, sizeof(internalIPAddress))) | ||||
{ | { | ||||
LOGMESSAGE("Net server: using cached IGD = %s", urls.controlURL); | LOGMESSAGE("Net server: using cached IGD = %s", urls.controlURL); | ||||
ret = 1; | ret = 1; | ||||
} | } | ||||
// No cached URL, or it did not respond. Try getting a valid UPnP device for 10 seconds. | // No cached URL, or it did not respond. Try getting a valid UPnP device for 10 seconds. | ||||
#if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 14 | #if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 14 | ||||
else if ((devlist = upnpDiscover(10000, 0, 0, 0, 0, 2, 0)) != NULL) | else if ((devlist = upnpDiscover(10000, 0, 0, 0, 0, 2, 0)) != nullptr) | ||||
#else | #else | ||||
else if ((devlist = upnpDiscover(10000, 0, 0, 0, 0, 0)) != NULL) | else if ((devlist = upnpDiscover(10000, 0, 0, 0, 0, 0)) != nullptr) | ||||
#endif | #endif | ||||
{ | { | ||||
ret = UPNP_GetValidIGD(devlist, &urls, &data, internalIPAddress, sizeof(internalIPAddress)); | ret = UPNP_GetValidIGD(devlist, &urls, &data, internalIPAddress, sizeof(internalIPAddress)); | ||||
allocatedUrls = ret != 0; // urls is allocated on non-zero return values | allocatedUrls = ret != 0; // urls is allocated on non-zero return values | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
LOGMESSAGE("Net server: upnpDiscover failed and no working cached URL."); | LOGMESSAGE("Net server: upnpDiscover failed and no working cached URL."); | ||||
return NULL; | return nullptr; | ||||
} | } | ||||
switch (ret) | switch (ret) | ||||
{ | { | ||||
case 0: | case 0: | ||||
LOGMESSAGE("Net server: No IGD found"); | LOGMESSAGE("Net server: No IGD found"); | ||||
break; | break; | ||||
case 1: | case 1: | ||||
Show All 9 Lines | default: | ||||
debug_warn(L"Unrecognized return value from UPNP_GetValidIGD"); | debug_warn(L"Unrecognized return value from UPNP_GetValidIGD"); | ||||
} | } | ||||
// Try getting our external/internet facing IP. TODO: Display this on the game-setup page for conviniance. | // Try getting our external/internet facing IP. TODO: Display this on the game-setup page for conviniance. | ||||
ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); | ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); | ||||
if (ret != UPNPCOMMAND_SUCCESS) | if (ret != UPNPCOMMAND_SUCCESS) | ||||
{ | { | ||||
LOGMESSAGE("Net server: GetExternalIPAddress failed with code %d (%s)", ret, strupnperror(ret)); | LOGMESSAGE("Net server: GetExternalIPAddress failed with code %d (%s)", ret, strupnperror(ret)); | ||||
return NULL; | return nullptr; | ||||
} | } | ||||
LOGMESSAGE("Net server: ExternalIPAddress = %s", externalIPAddress); | LOGMESSAGE("Net server: ExternalIPAddress = %s", externalIPAddress); | ||||
// Try to setup port forwarding. | // Try to setup port forwarding. | ||||
ret = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, psPort, psPort, | ret = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, psPort, psPort, | ||||
internalIPAddress, description, protocall, 0, leaseDuration); | internalIPAddress, description, protocall, 0, leaseDuration); | ||||
if (ret != UPNPCOMMAND_SUCCESS) | if (ret != UPNPCOMMAND_SUCCESS) | ||||
{ | { | ||||
LOGMESSAGE("Net server: AddPortMapping(%s, %s, %s) failed with code %d (%s)", | LOGMESSAGE("Net server: AddPortMapping(%s, %s, %s) failed with code %d (%s)", | ||||
psPort, psPort, internalIPAddress, ret, strupnperror(ret)); | psPort, psPort, internalIPAddress, ret, strupnperror(ret)); | ||||
return NULL; | return nullptr; | ||||
} | } | ||||
// Check that the port was actually forwarded. | // Check that the port was actually forwarded. | ||||
ret = UPNP_GetSpecificPortMappingEntry(urls.controlURL, | ret = UPNP_GetSpecificPortMappingEntry(urls.controlURL, | ||||
data.first.servicetype, | data.first.servicetype, | ||||
psPort, protocall, | psPort, protocall, | ||||
#if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 10 | #if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 10 | ||||
NULL/*remoteHost*/, | nullptr/*remoteHost*/, | ||||
#endif | #endif | ||||
intClient, intPort, NULL/*desc*/, | intClient, intPort, nullptr/*desc*/, | ||||
NULL/*enabled*/, duration); | nullptr/*enabled*/, duration); | ||||
if (ret != UPNPCOMMAND_SUCCESS) | if (ret != UPNPCOMMAND_SUCCESS) | ||||
{ | { | ||||
LOGMESSAGE("Net server: GetSpecificPortMappingEntry() failed with code %d (%s)", ret, strupnperror(ret)); | LOGMESSAGE("Net server: GetSpecificPortMappingEntry() failed with code %d (%s)", ret, strupnperror(ret)); | ||||
return NULL; | return nullptr; | ||||
} | } | ||||
LOGMESSAGE("Net server: External %s:%s %s is redirected to internal %s:%s (duration=%s)", | LOGMESSAGE("Net server: External %s:%s %s is redirected to internal %s:%s (duration=%s)", | ||||
externalIPAddress, psPort, protocall, intClient, intPort, duration); | externalIPAddress, psPort, protocall, intClient, intPort, duration); | ||||
// Cache root descriptor URL to try to avoid discovery next time. | // Cache root descriptor URL to try to avoid discovery next time. | ||||
g_ConfigDB.SetValueString(CFG_USER, "network.upnprootdescurl", urls.controlURL); | g_ConfigDB.SetValueString(CFG_USER, "network.upnprootdescurl", urls.controlURL); | ||||
g_ConfigDB.WriteValueToFile(CFG_USER, "network.upnprootdescurl", urls.controlURL); | g_ConfigDB.WriteValueToFile(CFG_USER, "network.upnprootdescurl", urls.controlURL); | ||||
LOGMESSAGE("Net server: cached UPnP root descriptor URL as %s", urls.controlURL); | LOGMESSAGE("Net server: cached UPnP root descriptor URL as %s", urls.controlURL); | ||||
// Make sure everything is properly freed. | // Make sure everything is properly freed. | ||||
if (allocatedUrls) | if (allocatedUrls) | ||||
FreeUPNPUrls(&urls); | FreeUPNPUrls(&urls); | ||||
freeUPNPDevlist(devlist); | freeUPNPDevlist(devlist); | ||||
return NULL; | return nullptr; | ||||
} | } | ||||
#endif // CONFIG2_MINIUPNPC | #endif // CONFIG2_MINIUPNPC | ||||
bool CNetServerWorker::SendMessage(ENetPeer* peer, const CNetMessage* message) | bool CNetServerWorker::SendMessage(ENetPeer* peer, const CNetMessage* message) | ||||
{ | { | ||||
ENSURE(m_Host); | ENSURE(m_Host); | ||||
CNetServerSession* session = static_cast<CNetServerSession*>(peer->data); | CNetServerSession* session = static_cast<CNetServerSession*>(peer->data); | ||||
Show All 19 Lines | |||||
} | } | ||||
void* CNetServerWorker::RunThread(void* data) | void* CNetServerWorker::RunThread(void* data) | ||||
{ | { | ||||
debug_SetThreadName("NetServer"); | debug_SetThreadName("NetServer"); | ||||
static_cast<CNetServerWorker*>(data)->Run(); | static_cast<CNetServerWorker*>(data)->Run(); | ||||
return NULL; | return nullptr; | ||||
} | } | ||||
void CNetServerWorker::Run() | void CNetServerWorker::Run() | ||||
{ | { | ||||
// The script runtime uses the profiler and therefore the thread must be registered before the runtime is created | // The script runtime uses the profiler and therefore the thread must be registered before the runtime is created | ||||
g_Profiler2.RegisterCurrentThread("Net server"); | g_Profiler2.RegisterCurrentThread("Net server"); | ||||
// To avoid the need for JS_SetContextThread, we create and use and destroy | // To avoid the need for JS_SetContextThread, we create and use and destroy | ||||
▲ Show 20 Lines • Show All 106 Lines • ▼ Show 20 Lines | case ENET_EVENT_TYPE_CONNECT: | ||||
// Set up a session object for this peer | // Set up a session object for this peer | ||||
CNetServerSession* session = new CNetServerSession(*this, event.peer); | CNetServerSession* session = new CNetServerSession(*this, event.peer); | ||||
m_Sessions.push_back(session); | m_Sessions.push_back(session); | ||||
SetupSession(session); | SetupSession(session); | ||||
ENSURE(event.peer->data == NULL); | ENSURE(event.peer->data == nullptr); | ||||
event.peer->data = session; | event.peer->data = session; | ||||
HandleConnect(session); | HandleConnect(session); | ||||
break; | break; | ||||
} | } | ||||
case ENET_EVENT_TYPE_DISCONNECT: | case ENET_EVENT_TYPE_DISCONNECT: | ||||
{ | { | ||||
// If there is an active session with this peer, then reset and delete it | // If there is an active session with this peer, then reset and delete it | ||||
CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data); | CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data); | ||||
if (session) | if (session) | ||||
{ | { | ||||
LOGMESSAGE("Net server: Disconnected %s", DebugName(session).c_str()); | LOGMESSAGE("Net server: Disconnected %s", DebugName(session).c_str()); | ||||
// Remove the session first, so we won't send player-update messages to it | // Remove the session first, so we won't send player-update messages to it | ||||
// when updating the FSM | // when updating the FSM | ||||
m_Sessions.erase(remove(m_Sessions.begin(), m_Sessions.end(), session), m_Sessions.end()); | m_Sessions.erase(remove(m_Sessions.begin(), m_Sessions.end(), session), m_Sessions.end()); | ||||
session->Update((uint)NMT_CONNECTION_LOST, NULL); | session->Update((uint)NMT_CONNECTION_LOST, nullptr); | ||||
delete session; | delete session; | ||||
event.peer->data = NULL; | event.peer->data = nullptr; | ||||
} | } | ||||
if (m_State == SERVER_STATE_LOADING) | if (m_State == SERVER_STATE_LOADING) | ||||
CheckGameLoadStatus(NULL); | CheckGameLoadStatus(nullptr); | ||||
break; | break; | ||||
} | } | ||||
case ENET_EVENT_TYPE_RECEIVE: | case ENET_EVENT_TYPE_RECEIVE: | ||||
{ | { | ||||
// If there is an active session with this peer, then process the message | // If there is an active session with this peer, then process the message | ||||
▲ Show 20 Lines • Show All 1,085 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator