Pārlūkot izejas kodu

Send event when client loses connection to server. (+31 squashed commits)
Squashed commits:
[dbf3cdb] Register the server after we have established a UDP connection.
[bfaa4d7] Use u for scanf instead of inttype.
[729093f] Added method to disconnect from master.
[0b8b1b1] Created a single method to start a server and register it with the master server.
[8034f89] Added more comments around kNet changes.
[100b47b] Cleanup on clean disconnects or on errors.
[0bae49a] Only send dummy packet when connecting to the external IP.
[c694142] Fixed check for connection success.
[acd9981] Pulled some methods out.
[9ab7ad1] Added a single method for managing state of the connection to the master server.
[d45cbca] Implemented connect to game server state machine.
[9bd5d05] Register game server with master server.
Added explicit states for master connection and game server connection.
[af2782e] Add connection to string message.
[bccb29a] Allow for string messages to be sent between client and server.
[76e31a8] Stop pinging client once it has connected.
[620b3cb] NAT punch through works and direct connections still work
[acdd965] Attempt a connection immediately.
[ae0651c] Do not close the socket on bad packets.
[8b1df03] NAT-punchthrough works!
[7f1cd06] Implement sending UDP packets to client.
[48765dd] Added connectionless socket support for NAT punch-through.
[fb48fde] Client can query list of servers
[a57015d] Register server commands
[a4ab388] UDP port registration working.
[9f3b616] Pulled master server communication into a separate class.
[390cb4b] Master server / NAT-punchthrough WIP.
[9a94a73] Add methods to set / get control buttons.
[94e59f5] Remove StartServerSimple.
Added Connection class to bindings.
[8d4701e] Fix for missing keystrokes in the code editor on OSX.
[ac021f4] Fixed typo.
[bd8e934] Networked scenes can now be used from Javascript.

Keith Johnston 9 gadi atpakaļ
vecāks
revīzija
1fd5f282ca

+ 1 - 3
Script/Packages/Atomic/Network.json

@@ -2,7 +2,5 @@
 	"name" : "Network",
 	"sources" : ["Source/Atomic/Network"],
 	"includes" : ["<Atomic/Network/Protocol.h>", "<Atomic/Scene/Scene.h>"],
-	"classes" : ["Network", "NetworkPriority", "HttpRequest"]
-
-
+	"classes" : ["Network", "NetworkPriority", "HttpRequest", "Connection"]
 }

+ 70 - 0
Source/Atomic/Network/Connection.cpp

@@ -60,6 +60,11 @@ PackageUpload::PackageUpload() :
 {
 }
 
+Connection::Connection(Context* context) : Object(context)
+{
+
+}
+
 Connection::Connection(Context* context, bool isClient, kNet::SharedPtr<kNet::MessageConnection> connection) :
     Object(context),
     timeStamp_(0),
@@ -200,6 +205,8 @@ void Connection::SetScene(Scene* newScene)
         scene_->StopAsyncLoading();
         SubscribeToEvent(scene_, E_ASYNCLOADFINISHED, HANDLER(Connection, HandleAsyncLoadFinished));
     }
+
+    SubscribeToEvent(scene_, E_COMPONENTREMOVED, HANDLER(Connection, HandleComponentRemoved));
 }
 
 void Connection::SetIdentity(const VariantMap& identity)
@@ -437,6 +444,9 @@ bool Connection::ProcessMessage(int msgID, MemoryBuffer& msg)
     case MSG_PACKAGEINFO:
         ProcessPackageInfo(msgID, msg);
         break;
+    case MSG_STRING:
+        ProcessStringMessage(msgID, msg);
+        break;
 
     default:
         processed = false;
@@ -1101,6 +1111,28 @@ void Connection::HandleAsyncLoadFinished(StringHash eventType, VariantMap& event
     SendMessage(MSG_SCENELOADED, true, true, msg_);
 }
 
+void Connection::HandleComponentRemoved(StringHash eventType, VariantMap& eventData)
+{
+    using namespace ComponentRemoved;
+
+    Component* comp = static_cast<Component*>(eventData[P_COMPONENT].GetPtr());
+    Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
+
+    unsigned nodeId = node->GetID();
+    unsigned compId = comp->GetID();
+
+    if (sceneState_.nodeStates_.Contains(node->GetID()))
+    {
+        NodeReplicationState& nodeState = sceneState_.nodeStates_[node->GetID()];
+        if (nodeState.componentStates_.Contains(comp->GetID()))
+        {
+            ComponentReplicationState& compState = nodeState.componentStates_[comp->GetID()];
+            compState.component_ = NULL;
+        }
+    }
+}
+
+
 void Connection::ProcessNode(unsigned nodeID)
 {
     // Check that we have not already processed this due to dependency recursion
@@ -1546,4 +1578,42 @@ void Connection::ProcessPackageInfo(int msgID, MemoryBuffer& msg)
     RequestNeededPackages(1, msg);
 }
 
+// Expose control methods for current controls
+void Connection::SetControlButtons(unsigned buttons, bool down)
+{
+    controls_.Set(buttons,down);
+}
+
+/// Check if a button is held down.
+bool Connection::IsControlButtonDown(unsigned button) const
+{
+    return (controls_.IsDown(button));
+}
+
+void Connection::SetControlDataInt(const String &key, int value) {
+    controls_.extraData_[key] = value;
+}
+
+int Connection::GetControlDataInt(const String &key) {
+    return controls_.extraData_[key].GetInt();
+}
+
+void Connection::SendStringMessage(const String& message)
+{
+    // Send the identity map now
+    VectorBuffer msg;
+    msg.WriteString(message);
+    SendMessage(MSG_STRING, true, true, msg);
+}
+
+void Connection::ProcessStringMessage(int msgID, MemoryBuffer &msg) {
+    using namespace NetworkMessage;
+
+    VariantMap &eventData = GetEventDataMap();
+    eventData[P_MESSAGEID] = (int) msgID;
+    eventData[P_CONNECTION] = this;
+    eventData[P_DATA] = msg.ReadString();
+    SendEvent(E_NETWORKSTRINGMESSAGE, eventData);
+}
+
 }

+ 19 - 0
Source/Atomic/Network/Connection.h

@@ -107,6 +107,7 @@ class ATOMIC_API Connection : public Object
     OBJECT(Connection);
 
 public:
+    Connection(Context* context);
     /// Construct with context and kNet message connection pointers.
     Connection(Context* context, bool isClient, kNet::SharedPtr<kNet::MessageConnection> connection);
     /// Destruct.
@@ -230,6 +231,19 @@ public:
     /// Identity map.
     VariantMap identity_;
 
+    // Expose control methods for current controls
+    void SetControlButtons(unsigned buttons, bool down = true);
+
+    /// Check if a button is held down.
+    bool IsControlButtonDown(unsigned button) const;
+
+    void SetControlDataInt(const String& key, int value);
+
+    int GetControlDataInt(const String& key);
+
+    /// Send a message.
+    void SendStringMessage(const String& message);
+
 private:
     /// Handle scene loaded event.
     void HandleAsyncLoadFinished(StringHash eventType, VariantMap& eventData);
@@ -270,6 +284,10 @@ private:
     /// Handle all packages loaded successfully. Also called directly on MSG_LOADSCENE if there are none.
     void OnPackagesReady();
 
+    void ProcessStringMessage(int msgID, MemoryBuffer& msg);
+
+    void HandleComponentRemoved(StringHash eventType, VariantMap& eventData);
+
     /// kNet message connection.
     kNet::SharedPtr<kNet::MessageConnection> connection_;
     /// Scene.
@@ -315,3 +333,4 @@ private:
 };
 
 }
+

+ 556 - 0
Source/Atomic/Network/MasterServerClient.cpp

@@ -0,0 +1,556 @@
+//
+// Created by Keith Johnston on 4/16/16.
+//
+
+#include "MasterServerClient.h"
+#include "../Precompiled.h"
+#include "../IO/Log.h"
+#include "../Network/NetworkPriority.h"
+#include "../Network/Network.h"
+#include "../Core/Profiler.h"
+#include "../Network/NetworkEvents.h"
+
+#include <rapidjson/document.h>
+#include <rapidjson/stringbuffer.h>
+#include <rapidjson/prettywriter.h>
+
+namespace Atomic
+{
+
+MasterServerClient::MasterServerClient(Context *context) :
+
+        readingMasterMessageLength(true),
+        Object(context),
+        udpSecondsTillRetry_(0.5f),
+        connectToMasterState_(MASTER_NOT_CONNECTED),
+        clientConnectToGameServerState_(GAME_NOT_CONNECTED),
+        timeBetweenClientPunchThroughAttempts_(1.0),
+        timeTillNextPunchThroughAttempt_(0.0),
+        timeBetweenClientConnectAttempts_(1.0),
+        timeTillNextClientConnectAttempt_(1.0),
+        masterTCPConnection_(NULL),
+        clientToServerSocket_(NULL)
+{
+
+}
+
+MasterServerClient::~MasterServerClient()
+{
+    if (masterTCPConnection_)
+    {
+        masterTCPConnection_->Disconnect();
+    }
+}
+
+void MasterServerClient::ConnectToMaster(const String &address, unsigned short port) {
+    PROFILE(ConnectToMaster);
+
+    if (connectToMasterState_ != MASTER_NOT_CONNECTED)
+    {
+        DisconnectFromMaster();
+    }
+
+    masterServerInfo_.address = address;
+    masterServerInfo_.port = port;
+    masterServerInfo_.isRegisteringServer = false;
+
+    // We first make a TCP connection
+    SetConnectToMasterState(MASTER_CONNECTING_TCP);
+}
+
+void MasterServerClient::DisconnectFromMaster()
+{
+    masterTCPConnection_->Disconnect();
+    masterUDPConnection_->Disconnect();
+
+    SetConnectToMasterState(MASTER_NOT_CONNECTED);
+}
+
+void MasterServerClient::ConnectToMasterAndRegister(const String &address, unsigned short port, const String& serverName) {
+    PROFILE(ConnectToMaster);
+
+    if (connectToMasterState_ != MASTER_NOT_CONNECTED)
+    {
+        DisconnectFromMaster();
+    }
+
+    masterServerInfo_.address = address;
+    masterServerInfo_.port = port;
+    masterServerInfo_.serverName = serverName;
+    masterServerInfo_.isRegisteringServer = true;
+
+    // We first make a TCP connection
+    SetConnectToMasterState(MASTER_CONNECTING_TCP);
+}
+
+void MasterServerClient::RequestServerListFromMaster()
+{
+    SendMessageToMasterServer("{ \"cmd\": \"getServerList\" }");
+}
+
+void MasterServerClient::ConnectToServerViaMaster(const String &serverId,
+                                                  const String &internalAddress, unsigned short internalPort,
+                                                  const String &externalAddress, unsigned short externalPort,
+                                                  Scene* scene)
+{
+    remoteGameServerInfo_.serverId = serverId;
+    remoteGameServerInfo_.internalAddress = internalAddress;
+    remoteGameServerInfo_.internalPort = internalPort;
+    remoteGameServerInfo_.externalAddress = externalAddress;
+    remoteGameServerInfo_.externalPort = externalPort;
+    remoteGameServerInfo_.clientScene = scene;
+
+    SetConnectToGameServerState(GAME_CONNECTING_INTERNAL_IP);
+}
+
+void MasterServerClient::RegisterServerWithMaster(const String &name)
+{
+    // Get the internal IP and port
+    kNet::EndPoint localEndPoint = masterTCPConnection_->LocalEndPoint();
+
+    unsigned char* ip = localEndPoint.ip;
+
+    char str[256];
+    sprintf(str, "%d.%d.%d.%d", (unsigned int)ip[0], (unsigned int)ip[1], (unsigned int)ip[2], (unsigned int)ip[3]);
+
+    Atomic::Network* network = GetSubsystem<Network>();
+    unsigned int localPort = network->GetServerPort();
+
+    String msg = String("{") +
+                 String("\"cmd\":") + String("\"registerServer\",") +
+                 String("\"internalIP\": \"") + String(str) + String("\", ") +
+                 String("\"internalPort\": ") + String(localPort) + String(", ") +
+                 String("\"id\":\"") + masterServerConnectionId_ + String("\", ") +
+                 String("\"serverName\":\"") + name + String("\"") +
+                 String("}");
+
+    SendMessageToMasterServer(msg);
+}
+
+void MasterServerClient::SendMessageToMasterServer(const String& msg)
+{
+    if (masterTCPConnection_)
+    {
+        String netString = String(msg.Length()) + ':' + msg;
+        masterTCPConnection_->Send(netString.CString(), netString.Length());
+    }
+    else
+    {
+        LOGERROR("No master server connection. Cannot send message");
+    }
+}
+
+void MasterServerClient::Update(float dt)
+{
+    if (masterTCPConnection_==NULL)
+    {
+        return;
+    }
+
+    CheckForMessageFromMaster();
+    ConnectToMasterUpdate(dt);
+    ConnectToGameServerUpdate(dt);
+    CheckForNatPunchThroughRequests(dt);
+}
+
+void MasterServerClient::CheckForNatPunchThroughRequests(float dt)
+{
+    if (clientIdToPunchThroughSocketMap_.Size() > 0)
+    {
+        if (timeTillNextPunchThroughAttempt_ <= 0)
+        {
+            for (HashMap<String, kNet::Socket*>::Iterator i = clientIdToPunchThroughSocketMap_.Begin();
+                 i != clientIdToPunchThroughSocketMap_.End();)
+            {
+                Network* network = GetSubsystem<Network>();
+                LOGINFO("Sending packet to client");
+                kNet::Socket* s = i->second_;
+
+                if (network->IsEndPointConnected(s->RemoteEndPoint()))
+                {
+                    i = clientIdToPunchThroughSocketMap_.Erase(i);
+                }
+                else
+                {
+                    s->Send("K",1);
+                    ++i;
+                }
+            }
+
+            // Reset the timer
+            timeTillNextPunchThroughAttempt_ = timeBetweenClientPunchThroughAttempts_;
+        }
+
+        timeTillNextPunchThroughAttempt_ -= dt;
+    }
+
+    // See if we need to still be trying to punch through from the client
+    if (clientConnectToGameServerState_ == GAME_CONNECTING_EXTERNAL_IP &&
+        clientToServerSocket_ != NULL && timeTillNextClientConnectAttempt_ <= 0)
+    {
+        Atomic::Network* network = GetSubsystem<Network>();
+
+        if (!network->GetServerConnection() || !network->GetServerConnection()->IsConnected())
+        {
+            LOGINFO("Sending packet to server");
+            clientToServerSocket_->Send("K",1);
+        }
+
+        timeTillNextClientConnectAttempt_ = timeBetweenClientConnectAttempts_;
+    }
+
+    timeTillNextClientConnectAttempt_ -= dt;
+}
+
+void MasterServerClient::CheckForMessageFromMaster()
+{
+    kNet::OverlappedTransferBuffer *buf = masterTCPConnection_->BeginReceive();
+
+    if (buf && buf->bytesContains > 0) {
+
+        int n = 0;
+        int totalBytes = buf->bytesContains;
+
+        while (n < totalBytes) {
+            char c = buf->buffer.buf[n++];
+
+            // Are we still reading in the length?
+            if (readingMasterMessageLength) {
+                if (c == ':') {
+                    sscanf(masterMessageLengthStr.CString(), "%u" , &bytesRemainingInMasterServerMessage_);
+                    readingMasterMessageLength = false;
+
+                    LOGINFO("Message is " + String(bytesRemainingInMasterServerMessage_) + " long");
+                }
+                else {
+                    masterMessageLengthStr += c;
+                }
+            }
+            else {
+                // Are we reading in the string?
+
+                masterMessageStr += c;
+                bytesRemainingInMasterServerMessage_--;
+
+                // Did we hit the end of the string?
+                if (bytesRemainingInMasterServerMessage_ == 0) {
+                    HandleMasterServerMessage(masterMessageStr);
+                    readingMasterMessageLength = true;
+                    masterMessageLengthStr = "";
+                    masterMessageStr = "";
+                }
+            }
+        }
+
+        masterTCPConnection_->EndReceive(buf);
+    }
+}
+
+void MasterServerClient::ConnectToMasterUpdate(float dt)
+{
+    if (connectToMasterState_ == MASTER_NOT_CONNECTED ||
+        connectToMasterState_ == MASTER_CONNECTION_FAILED)
+    {
+        return;
+    }
+
+    if (connectToMasterState_ == MASTER_CONNECTING_UDP)
+    {
+        if (udpConnectionSecondsRemaining_ <= 0)
+        {
+            connectToMasterState_ = MASTER_CONNECTION_FAILED;
+
+            // TODO - emit error event
+
+            return;
+        }
+
+        if (udpSecondsTillRetry_ <= 0)
+        {
+            String msg = "{ \"cmd\": \"registerUDPPort\", \"id\": \"" + masterServerConnectionId_ + "\"}";
+
+            masterUDPConnection_->Send(msg.CString(), msg.Length());
+            udpSecondsTillRetry_ = 0.5;
+        }
+        else
+        {
+            udpSecondsTillRetry_ -= dt;
+            udpConnectionSecondsRemaining_ -= dt;
+        }
+    }
+
+}
+
+void MasterServerClient::SetConnectToMasterState(ConnectToMasterState state)
+{
+    Atomic::Network* network = GetSubsystem<Network>();
+    kNet::Network* kNetNetwork = network->GetKnetNetwork();
+
+    if (connectToMasterState_ == MASTER_NOT_CONNECTED &&
+        state == MASTER_CONNECTING_TCP)
+    {
+        masterTCPConnection_ = kNetNetwork->ConnectSocket(masterServerInfo_.address.CString(),
+                                                          masterServerInfo_.port, kNet::SocketOverTCP);
+
+        if (!masterTCPConnection_)
+        {
+            SetConnectToMasterState(MASTER_CONNECTION_FAILED);
+        }
+        else
+        {
+            SendMessageToMasterServer("{ \"cmd\": \"connectRequest\" }");
+        }
+    }
+    else if (connectToMasterState_ == MASTER_CONNECTING_TCP &&
+             state == MASTER_CONNECTING_UDP)
+    {
+        Atomic::Network* network = GetSubsystem<Network>();
+        kNet::Network* kNetNetwork = network->GetKnetNetwork();
+
+        kNet::NetworkServer* server = kNetNetwork->GetServer().ptr();
+
+        kNet::EndPoint masterEndPoint;
+
+        sscanf(masterServerInfo_.address.CString(), "%hu.%hu.%hu.%hu",
+               (unsigned short *) &masterEndPoint.ip[0],
+               (unsigned short *) &masterEndPoint.ip[1],
+               (unsigned short *) &masterEndPoint.ip[2],
+               (unsigned short *) &masterEndPoint.ip[3]);
+
+        masterEndPoint.port = masterServerInfo_.port;
+
+
+        if (server)
+        {
+            std::vector < kNet::Socket * > listenSockets = kNetNetwork->GetServer()->ListenSockets();
+
+            kNet::Socket *listenSocket = listenSockets[0];
+
+            // Create a UDP and a TCP connection to the master server
+            // UDP connection re-uses the same udp port we are listening on for the sever
+            masterUDPConnection_ = new kNet::Socket(listenSocket->GetSocketHandle(),
+                                                    listenSocket->LocalEndPoint(),
+                                                    listenSocket->LocalAddress(), masterEndPoint, "",
+                                                    kNet::SocketOverUDP, kNet::ServerClientSocket, 1400);
+        }
+        else
+        {
+            masterUDPConnection_ = kNetNetwork->CreateUnconnectedUDPSocket(masterServerInfo_.address.CString(),
+                                                                           masterServerInfo_.port);
+        }
+    }
+
+    if (state == MASTER_CONNECTION_FAILED)
+    {
+        LOGERROR("Could not connect to master server");
+        SendEvent(E_MASTERCONNECTIONFAILED);
+    }
+
+    if (state == MASTER_CONNECTED)
+    {
+        SendEvent(E_MASTERCONNECTIONREADY);
+    }
+
+    connectToMasterState_ = state;
+}
+
+void MasterServerClient::SetConnectToGameServerState(ClientConnectToGameServerState state)
+{
+    if (clientConnectToGameServerState_ == GAME_NOT_CONNECTED &&
+        state == GAME_CONNECTING_INTERNAL_IP)
+    {
+        kNet::EndPoint serverEndPoint;
+        sscanf(remoteGameServerInfo_.internalAddress.CString(), "%hu.%hu.%hu.%hu",
+               (unsigned short *) &serverEndPoint.ip[0],
+               (unsigned short *) &serverEndPoint.ip[1],
+               (unsigned short *) &serverEndPoint.ip[2],
+               (unsigned short *) &serverEndPoint.ip[3]);
+
+        serverEndPoint.port = remoteGameServerInfo_.internalPort;
+
+        clientToServerSocket_ = new kNet::Socket(masterUDPConnection_->GetSocketHandle(),
+                                                 masterUDPConnection_->LocalEndPoint(),
+                                                 masterUDPConnection_->LocalAddress(), serverEndPoint, "",
+                                                 kNet::SocketOverUDP, kNet::ClientConnectionLessSocket, 1400);
+
+        Atomic::Network* network = GetSubsystem<Network>();
+        network->ConnectWithExistingSocket(clientToServerSocket_, remoteGameServerInfo_.clientScene);
+
+        connectToGameServerSecondsRemaining_ = 5.0f;
+
+        LOGINFO("Connecting to Game Server on Internal IP: " +
+                        remoteGameServerInfo_.internalAddress + ":" +
+                        String(remoteGameServerInfo_.internalPort));
+    }
+    else if (clientConnectToGameServerState_ == GAME_CONNECTING_INTERNAL_IP &&
+             state == GAME_CONNECTING_EXTERNAL_IP)
+    {
+        // Ask the master server to tell the game server
+        // we want to connect to it.
+        String msg = String("{") +
+                     String("\"cmd\":") + String("\"requestIntroduction\",") +
+                     String("\"id\":\"") + masterServerConnectionId_ + String("\", ") +
+                     String("\"serverId\":\"") + remoteGameServerInfo_.serverId + String("\"") +
+                     String("}");
+
+        SendMessageToMasterServer(msg);
+
+        kNet::EndPoint serverEndPoint;
+        sscanf(remoteGameServerInfo_.externalAddress.CString(), "%hu.%hu.%hu.%hu",
+               (unsigned short *) &serverEndPoint.ip[0],
+               (unsigned short *) &serverEndPoint.ip[1],
+               (unsigned short *) &serverEndPoint.ip[2],
+               (unsigned short *) &serverEndPoint.ip[3]);
+
+        serverEndPoint.port = remoteGameServerInfo_.externalPort;
+
+        clientToServerSocket_ = new kNet::Socket(masterUDPConnection_->GetSocketHandle(),
+                                                 masterUDPConnection_->LocalEndPoint(),
+                                                 masterUDPConnection_->LocalAddress(), serverEndPoint, "",
+                                                 kNet::SocketOverUDP, kNet::ClientConnectionLessSocket, 1400);
+
+        Atomic::Network* network = GetSubsystem<Network>();
+        network->ConnectWithExistingSocket(clientToServerSocket_, remoteGameServerInfo_.clientScene);
+
+        connectToGameServerSecondsRemaining_ = 5.0f;
+
+        LOGINFO("Connecting to Game Server on External IP: " +
+                remoteGameServerInfo_.externalAddress + ":" +
+                String(remoteGameServerInfo_.externalPort));
+
+    }
+
+    if (state == GAME_CONNECTION_FAILED)
+    {
+        SendEvent(E_CONNECTFAILED);
+    }
+
+    clientConnectToGameServerState_ = state;
+}
+
+void MasterServerClient::ConnectToGameServerUpdate(float dt)
+{
+    if (clientConnectToGameServerState_ == GAME_NOT_CONNECTED ||
+        clientConnectToGameServerState_ == GAME_CONNECTED ||
+        clientConnectToGameServerState_ == GAME_CONNECTION_FAILED)
+    {
+       return;
+    }
+
+    Atomic::Network* network = GetSubsystem<Network>();
+
+    // If we are connected then set the final state
+    if (network->GetServerConnection() && network->GetServerConnection()->IsConnected())
+    {
+        if (clientConnectToGameServerState_ == GAME_CONNECTING_INTERNAL_IP)
+        {
+            LOGINFO("Successfully connected using internal IP");
+        }
+        else if (clientConnectToGameServerState_ == GAME_CONNECTING_EXTERNAL_IP)
+        {
+            LOGINFO("Successfully connected using external IP");
+        }
+
+        SetConnectToGameServerState(GAME_CONNECTED);
+        return;
+    }
+
+    if (connectToGameServerSecondsRemaining_ <= 0)
+    {
+        if (clientConnectToGameServerState_ == GAME_CONNECTING_INTERNAL_IP)
+        {
+            LOGINFO("Unable to connect via internal IP, trying external IP");
+            SetConnectToGameServerState(GAME_CONNECTING_EXTERNAL_IP);
+        }
+        else
+        {
+            SetConnectToGameServerState(GAME_CONNECTION_FAILED);
+        }
+        return;
+    }
+
+    connectToGameServerSecondsRemaining_ -= dt;
+}
+
+void MasterServerClient::HandleMasterServerMessage(const String &msg)
+{
+    LOGINFO("Got master server message: " + msg);
+
+    rapidjson::Document document;
+    if (document.Parse<0>(msg.CString()).HasParseError())
+    {
+        LOGERROR("Could not parse JSON data from string");
+        return;
+    }
+
+    String cmd = document["cmd"].GetString();
+
+    bool sendEvent = false;
+
+    if (cmd == "connectTCPSuccess")
+    {
+        udpSecondsTillRetry_ = 0;
+        udpConnectionSecondsRemaining_ = 5.0;
+        masterServerConnectionId_ = document["id"].GetString();
+
+        // Now connect with UDP
+        SetConnectToMasterState(MASTER_CONNECTING_UDP);
+        LOGINFO("TCP Connected");
+    }
+    else if (cmd == "connectUDPSuccess")
+    {
+        SetConnectToMasterState(MASTER_CONNECTED);
+
+        // Register server if needed
+        if (masterServerInfo_.isRegisteringServer)
+        {
+            RegisterServerWithMaster(masterServerInfo_.serverName);
+        }
+
+        LOGINFO("UDP Connected");
+    }
+    else if (cmd == "sendPacketToClient")
+    {
+        String clientIP = document["clientIP"].GetString();
+        int clientPort = document["clientPort"].GetInt();
+
+        LOGINFO("Got request to send packet to client at "+clientIP+":"+String(clientPort));
+
+        kNet::EndPoint clientEndPoint;
+
+        sscanf(clientIP.CString(), "%hu.%hu.%hu.%hu",
+               (unsigned short *) &clientEndPoint.ip[0],
+               (unsigned short *) &clientEndPoint.ip[1],
+               (unsigned short *) &clientEndPoint.ip[2],
+               (unsigned short *) &clientEndPoint.ip[3]);
+
+
+        clientEndPoint.port = clientPort;
+
+        // Create a socket that goes out the same port we are listening on to the client.
+        // This will be used until the client actually connects.
+        kNet::Socket* s = new kNet::Socket(masterUDPConnection_->GetSocketHandle(),
+                                           masterUDPConnection_->LocalEndPoint(),
+                                           masterUDPConnection_->LocalAddress(), clientEndPoint, "",
+                                           kNet::SocketOverUDP, kNet::ServerClientSocket, 1400);
+
+        String clientId = document["clientId"].GetString();
+        clientIdToPunchThroughSocketMap_.Insert(MakePair(clientId, s));
+    }
+    else if (cmd == "serverList")
+    {
+        sendEvent = true;
+    }
+
+    if (sendEvent)
+    {
+        using namespace NetworkMessage;
+
+        VariantMap& eventData = GetEventDataMap();
+        eventData[P_DATA] = msg;
+        SendEvent(E_MASTERMESSAGE, eventData);
+    }
+}
+
+
+}

+ 128 - 0
Source/Atomic/Network/MasterServerClient.h

@@ -0,0 +1,128 @@
+//
+// Communication with an atomic master server
+// Handles NAT Punch-through for clients
+//
+
+#pragma once
+
+#include <ThirdParty/kNet/include/kNet.h>
+#include "../Core/Object.h"
+
+namespace Atomic
+{
+
+class Scene;
+
+enum ConnectToMasterState
+{
+    MASTER_NOT_CONNECTED = 0,
+    MASTER_CONNECTING_UDP,
+    MASTER_CONNECTING_TCP,
+    MASTER_CONNECTED,
+    MASTER_CONNECTION_FAILED
+};
+
+enum ClientConnectToGameServerState
+{
+    GAME_NOT_CONNECTED = 0,
+    GAME_CONNECTING_INTERNAL_IP,
+    GAME_VERIFYING_SERVER,
+    GAME_CONNECTING_EXTERNAL_IP,
+    GAME_CONNECTED,
+    GAME_CONNECTION_FAILED
+};
+
+struct MasterServer
+{
+    String address;
+    unsigned short port;
+    bool isRegisteringServer;
+    String serverName;
+};
+
+/// Game server info used by a client when connecting
+struct RemoteGameServer
+{
+    /// Game server id
+    String serverId;
+    String internalAddress;
+    unsigned short internalPort;
+    String externalAddress;
+    unsigned short externalPort;
+    Scene* clientScene;
+};
+
+
+/// %MasterServerClient subsystem.
+class ATOMIC_API MasterServerClient : public Object
+{
+    OBJECT(MasterServerClient);
+
+public:
+    /// Construct.
+    MasterServerClient(Context* context);
+    /// Destruct.
+    ~MasterServerClient();
+
+    /// Process messages
+    void Update(float timeStep);
+
+    void ConnectToMaster(const String& address, unsigned short port);
+    void DisconnectFromMaster();
+
+    void ConnectToMasterAndRegister(const String& address, unsigned short port, const String& serverName);
+
+    void RequestServerListFromMaster();
+    void ConnectToServerViaMaster(const String& serverId,
+                                  const String& internalAddress, unsigned short internalPort,
+                                  const String& externalAddress, unsigned short externalPort,
+                                  Scene* scene);
+
+private:
+    void SendMessageToMasterServer(const String& message);
+    void HandleMasterServerMessage(const String& msg);
+
+    void RegisterServerWithMaster(const String& serverName);
+
+    float udpConnectionSecondsRemaining_;
+    float udpSecondsTillRetry_;
+
+    // Communication with Master Server
+    bool readingMasterMessageLength;
+    uint32_t bytesRemainingInMasterServerMessage_;
+    String masterMessageLengthStr;
+    String masterMessageStr;
+    kNet::Socket* masterUDPConnection_;
+    kNet::Socket* masterTCPConnection_;
+
+    String masterServerConnectionId_;
+
+    void SetConnectToGameServerState(ClientConnectToGameServerState state);
+    void SetConnectToMasterState(ConnectToMasterState state);
+
+    void ConnectToMasterUpdate(float dt);
+    void ConnectToGameServerUpdate(float dt);
+
+    float timeBetweenClientPunchThroughAttempts_;
+    float timeTillNextPunchThroughAttempt_;
+
+    float timeBetweenClientConnectAttempts_;
+    float timeTillNextClientConnectAttempt_;
+
+    HashMap<String, kNet::Socket*> clientIdToPunchThroughSocketMap_;
+    kNet::Socket* clientToServerSocket_;
+
+    ConnectToMasterState connectToMasterState_;
+    ClientConnectToGameServerState clientConnectToGameServerState_;
+
+    RemoteGameServer remoteGameServerInfo_;
+    float connectToGameServerSecondsRemaining_;
+
+    MasterServer masterServerInfo_;
+
+    void CheckForMessageFromMaster();
+
+    void CheckForNatPunchThroughRequests(float dt);
+};
+
+}

+ 102 - 1
Source/Atomic/Network/Network.cpp

@@ -53,7 +53,8 @@ Network::Network(Context* context) :
     simulatedLatency_(0),
     simulatedPacketLoss_(0.0f),
     updateInterval_(1.0f / (float)DEFAULT_UPDATE_FPS),
-    updateAcc_(0.0f)
+    updateAcc_(0.0f),
+    masterServerClient_(context)
 {
     network_ = new kNet::Network();
 
@@ -203,6 +204,42 @@ void Network::ClientDisconnected(kNet::MessageConnection* connection)
     }
 }
 
+bool Network::ConnectSimple(const String& address, unsigned short port, Scene* scene)
+{
+    return Connect(address, port, scene, Variant::emptyVariantMap);
+}
+
+bool Network::ConnectWithExistingSocket(kNet::Socket* existingSocket, Scene* scene)
+{
+    PROFILE(ConnectWithExistingSocket);
+
+    // If a previous connection already exists, disconnect it and wait for some time for the connection to terminate
+    if (serverConnection_)
+    {
+        serverConnection_->Disconnect(100);
+        OnServerDisconnected();
+    }
+
+    kNet::SharedPtr<kNet::MessageConnection> connection = network_->Connect(existingSocket, this);
+    if (connection)
+    {
+        serverConnection_ = new Connection(context_, false, connection);
+        serverConnection_->SetScene(scene);
+        serverConnection_->SetIdentity(Variant::emptyVariantMap);
+        serverConnection_->SetConnectPending(true);
+        serverConnection_->ConfigureNetworkSimulator(simulatedLatency_, simulatedPacketLoss_);
+
+        LOGINFO("Connecting to server " + serverConnection_->ToString());
+        return true;
+    }
+    else
+    {
+        LOGERROR("Failed to connect to server ");
+        SendEvent(E_CONNECTFAILED);
+        return false;
+    }
+}
+
 bool Network::Connect(const String& address, unsigned short port, Scene* scene, const VariantMap& identity)
 {
     PROFILE(Connect);
@@ -250,9 +287,12 @@ bool Network::StartServer(unsigned short port)
 
     PROFILE(StartServer);
 
+    serverPort_ = port;
+
     if (network_->StartServer(port, kNet::SocketOverUDP, this, true) != 0)
     {
         LOGINFO("Started server on port " + String(port));
+
         return true;
     }
     else
@@ -425,6 +465,22 @@ Connection* Network::GetConnection(kNet::MessageConnection* connection) const
     }
 }
 
+bool Network::IsEndPointConnected(const kNet::EndPoint& endPoint) const
+{
+    Vector<SharedPtr<Connection> > ret;
+    for (HashMap<kNet::MessageConnection*, SharedPtr<Connection> >::ConstIterator i = clientConnections_.Begin();
+         i != clientConnections_.End(); ++i)
+    {
+        kNet::EndPoint remoteEndPoint = i->first_->GetSocket()->RemoteEndPoint();
+        if (endPoint.ToString()==remoteEndPoint.ToString())
+        {
+            return i->second_->IsConnected();
+        }
+    }
+
+    return false;
+}
+
 Connection* Network::GetServerConnection() const
 {
     return serverConnection_;
@@ -479,6 +535,8 @@ void Network::Update(float timeStep)
     kNet::SharedPtr<kNet::NetworkServer> server = network_->GetServer();
     if (server)
         server->Process();
+
+    masterServerClient_.Update(timeStep);
 }
 
 void Network::PostUpdate(float timeStep)
@@ -596,6 +654,49 @@ void Network::ConfigureNetworkSimulator()
         i->second_->ConfigureNetworkSimulator(simulatedLatency_, simulatedPacketLoss_);
 }
 
+void Network::ClientConnectToMaster(const String& address, unsigned short port)
+{
+    masterServerClient_.ConnectToMaster(address, port);
+}
+
+void Network::ClientDisconnectFromMaster()
+{
+    masterServerClient_.DisconnectFromMaster();
+}
+
+void Network::RequestServerListFromMaster()
+{
+    masterServerClient_.RequestServerListFromMaster();
+}
+
+void Network::ClientConnectToServerViaMaster(const String &serverId,
+                                       const String &internalAddress, unsigned short internalPort,
+                                       const String &externalAddress, unsigned short externalPort,
+                                       Scene* scene)
+{
+    masterServerClient_.ConnectToServerViaMaster(serverId,
+                                                 internalAddress, internalPort,
+                                                 externalAddress, externalPort,
+                                                 scene);
+}
+
+bool Network::StartServerAndRegisterWithMaster(unsigned short serverPort, const String &masterAddress,
+                                               unsigned short masterPort, const String &serverName)
+{
+    // First start the server
+    bool rc = StartServer(serverPort);
+
+    if (!rc)
+    {
+        return false;
+    }
+
+    // Connect to the master server
+    masterServerClient_.ConnectToMasterAndRegister(masterAddress, masterPort, serverName);
+
+    return true;
+}
+
 void RegisterNetworkLibrary(Context* context)
 {
     NetworkPriority::RegisterObject(context);

+ 35 - 1
Source/Atomic/Network/Network.h

@@ -26,9 +26,11 @@
 #include "../Core/Object.h"
 #include "../IO/VectorBuffer.h"
 #include "../Network/Connection.h"
+#include "../Network/MasterServerClient.h"
 
 #include <kNet/IMessageHandler.h>
 #include <kNet/INetworkServerListener.h>
+#include <ThirdParty/kNet/include/kNet/EndPoint.h>
 
 namespace Atomic
 {
@@ -37,7 +39,7 @@ class HttpRequest;
 class MemoryBuffer;
 class Scene;
 
-/// MessageConnection hash function.
+    /// MessageConnection hash function.
 template <class T> unsigned MakeHash(kNet::MessageConnection* value)
 {
     return ((unsigned)(size_t)value) >> 9;
@@ -66,10 +68,29 @@ public:
 
     /// Connect to a server using UDP protocol. Return true if connection process successfully started.
     bool Connect(const String& address, unsigned short port, Scene* scene, const VariantMap& identity = Variant::emptyVariantMap);
+
+    bool ConnectSimple(const String& address, unsigned short port, Scene* scene);
+
+    bool ConnectWithExistingSocket(kNet::Socket* existingSocket, Scene* scene);
+
+    void ClientConnectToMaster(const String& address, unsigned short port);
+    void ClientDisconnectFromMaster();
+
+    void ClientConnectToServerViaMaster(const String& serverId,
+                                        const String& internalAddress, unsigned short internalPort,
+                                        const String& externalAddress, unsigned short externalPort,
+                                        Scene* scene);
+    void RequestServerListFromMaster();
+
     /// Disconnect the connection to the server. If wait time is non-zero, will block while waiting for disconnect to finish.
     void Disconnect(int waitMSec = 0);
     /// Start a server on a port using UDP protocol. Return true if successful.
     bool StartServer(unsigned short port);
+
+    /// Start a server on a port using UDP protocol. Return true if successful.
+    /// Also register the server with the master server.
+    bool StartServerAndRegisterWithMaster(unsigned short serverPort, const String& masterAddress, unsigned short masterPort, const String& serverName);
+
     /// Stop the server.
     void StopServer();
     /// Broadcast a message with content ID to all client connections.
@@ -117,6 +138,9 @@ public:
 
     /// Return a client or server connection by kNet MessageConnection, or null if none exist.
     Connection* GetConnection(kNet::MessageConnection* connection) const;
+    // Return the connection with the matching endpoint
+    bool IsEndPointConnected(const kNet::EndPoint& endPoint) const;
+
     /// Return the connection to the server. Null if not connected.
     Connection* GetServerConnection() const;
     /// Return all client connections.
@@ -134,6 +158,10 @@ public:
     /// Send outgoing messages after frame logic. Called by HandleRenderUpdate.
     void PostUpdate(float timeStep);
 
+    kNet::Network* GetKnetNetwork() { return network_; }
+
+    unsigned int GetServerPort() { return serverPort_; }
+
 private:
     /// Handle begin frame event.
     void HandleBeginFrame(StringHash eventType, VariantMap& eventData);
@@ -145,6 +173,8 @@ private:
     void OnServerDisconnected();
     /// Reconfigure network simulator parameters on all existing connections.
     void ConfigureNetworkSimulator();
+    void HandleClientConnected(StringHash eventType, VariantMap& eventData);
+
 
     /// kNet instance.
     kNet::Network* network_;
@@ -170,6 +200,10 @@ private:
     float updateAcc_;
     /// Package cache directory.
     String packageCacheDir_;
+    // MasterServerClient
+    MasterServerClient masterServerClient_;
+    // Server Port
+    unsigned int serverPort_;
 };
 
 /// Register Network library objects.

+ 22 - 0
Source/Atomic/Network/NetworkEvents.h

@@ -97,4 +97,26 @@ EVENT(E_REMOTEEVENTDATA, RemoteEventData)
     PARAM(P_CONNECTION, Connection);      // Connection pointer
 }
 
+/// Master server connection ready
+EVENT(E_MASTERCONNECTIONREADY, MasterConnectionReady)
+{
+}
+
+/// Master server connection failed
+EVENT(E_MASTERCONNECTIONFAILED, MasterConnectionFailed)
+{
+}
+
+/// Unhandled master message received.
+EVENT(E_MASTERMESSAGE, MasterServerMessage)
+{
+    PARAM(P_DATA, Data);                    // Buffer
+}
+
+/// Unhandled master message received.
+EVENT(E_NETWORKSTRINGMESSAGE, NetworkStringMessage)
+{
+    PARAM(P_CONNECTION, Connection);      // Connection pointer
+    PARAM(P_DATA, Data);                    // Buffer
+}
 }

+ 3 - 0
Source/Atomic/Network/Protocol.h

@@ -64,6 +64,9 @@ static const int MSG_REMOTENODEEVENT = 0x15;
 /// Server->client: info about package.
 static const int MSG_PACKAGEINFO = 0x16;
 
+/// Server->client, Client->server: string message
+static const int MSG_STRING = 0x17;
+
 /// Fixed content ID for client controls update.
 static const unsigned CONTROLS_CONTENT_ID = 1;
 /// Package file fragment size.

+ 284 - 0
Source/AtomicMasterServer/app.js

@@ -0,0 +1,284 @@
+const dgram = require('dgram');
+const server = dgram.createSocket('udp4');
+var net = require('net');
+var events = require('events');
+var uuid = require('uuid');
+var _ = require('lodash');
+
+var messageEventEmitter = new events.EventEmitter();
+
+var connections = [];
+var serverList = [];
+
+var MASTER_SERVER_PORT = 41234;
+
+// -------------------------------------------------------------------
+//
+// Atomic Master Server
+//
+//
+// This server is designed to work with the MasterServerClient
+// in the Atomic Game Engine to allow multiplayer servers to register
+// themselves, in order to be discovered by multiplayer clients.
+//
+// It also handles NAT punchthrough in case the server is behind
+// a NAT firewall.
+//
+// -------------------------------------------------------------------
+
+// -------------------------------------------------------------------
+// UDP Server
+//
+// We use a UDP Socket to listen for messages from both clients
+// and servers. This way we can identify their external UDP ports
+// assigned by any NAT firewall they might be behind.
+//
+// We do not send any UDP packets back. We acknowledge that we
+// received the UDP port by sending a message back on the TCP socket.
+//
+// -------------------------------------------------------------------
+
+function writeMessageToSocket(sock, msgObj) {
+    var msg = JSON.stringify(msgObj);
+
+    var len = msg.length;
+
+    sock.write(len.toString());
+    sock.write(':');
+    sock.write(msg);
+}
+
+function handleServerUDPMessage(rinfo, msgObj) {
+    console.log('Processing UDP message: ' + JSON.stringify(msgObj));
+
+    if (msgObj.cmd === 'registerUDPPort') {
+        var connectionId = msgObj.id;
+
+        var connectionObj = _.find(connections, { connectionId: connectionId });
+
+        if (!connectionObj) {
+            console.error('No server found for id: '+connectionId);
+            return;
+        }
+
+        // Save the UDP port
+        connectionObj.externalUDPPort = rinfo.port;
+
+        // Send the success message
+        var udpPortMessage = {
+            cmd: 'connectUDPSuccess'
+        };
+
+        writeMessageToSocket(connectionObj.tcpSocket, udpPortMessage);
+
+        console.log('Got udp port:' + connectionObj.externalUDPPort);
+    } else {
+        console.log('Unable to process message: ' + msg)
+    }
+}
+
+// Set up UDP
+server.on('error', function(err) {
+    console.log("server error: "+ err.stack);
+    server.close();
+});
+
+server.on('message', function (msg, rinfo) {
+    console.log('Received %d bytes from %s:%d\n', msg.length, rinfo.address, rinfo.port);
+
+    try {
+        var msgObj = JSON.parse(msg);
+        handleServerUDPMessage(rinfo, msgObj);
+    } catch (err) {
+        console.error(err);
+        console.error(err.stack);
+        console.log('Unable to parse JSON from UDP: ' + message)
+    }
+});
+
+server.on('listening', function () {
+    var address = server.address();
+    console.log("udp server listening on "+address.address + ":" + address.port);
+});
+
+server.bind(MASTER_SERVER_PORT);
+
+// -------------------------------------------------------------------
+// TCP Server
+//
+// We use a TCP connection to handle requests from clients and from
+// servers. If the server TCP connection dies, we remove the server
+// from our list.
+//
+// Messages are received in a simplified 'netstring' format, where
+// the message length is followed by a colon, and then the actual
+// message.
+//
+// Example: "13:Hello, World!"
+//
+// Messages may not be complete until multiple data calls have
+// been made. Once we have a complete message, we fire the 'msg'
+// event on an event emitter and handle the message.
+// -------------------------------------------------------------------
+
+
+function handleServerTCPMessage(socket, msgObj) {
+    console.log('Processing TCP message: ' + JSON.stringify(msgObj));
+
+    if (msgObj.cmd === 'connectRequest') {
+        var connectionId = uuid.v4();
+
+        var connectionObj = {
+            connectionId: connectionId,
+            internalIP: msgObj.internalIP,
+            internalPort: msgObj.internalPort,
+            externalIP: socket.remoteAddress,
+            externalTCPPort: socket.remotePort,
+            tcpSocket: socket
+        };
+
+        connections.push(connectionObj);
+
+        // Send the uuid back to the game server
+        var registerSuccessMessage = {
+            cmd: 'connectTCPSuccess',
+            id: connectionId
+        };
+
+        writeMessageToSocket(socket, registerSuccessMessage);
+
+        console.log('Registered connection from IP:' + connectionObj.externalIP);
+    } else if (msgObj.cmd === 'getServerList' ) {
+
+        var servers = _.map(serverList, function (item) {
+            return _.pick(item, ['connectionId', 'internalIP', 'internalPort', 'externalIP', 'externalUDPPort', 'serverName' ]);
+        })
+
+        var response = {
+            cmd: 'serverList',
+            servers: JSON.stringify(servers)
+        }
+
+        writeMessageToSocket(socket, response);
+    } else if (msgObj.cmd === 'registerServer' ) {
+        var connectionInfo = _.find(connections, { connectionId: msgObj.id });
+
+        if (!connectionInfo) {
+            console.error("No server found: " + msgObj.id);
+        }
+
+        var serverInfo = _.clone(connectionInfo);
+        serverInfo.serverName = msgObj.serverName;
+        serverInfo.internalIP = msgObj.internalIP;
+        serverInfo.internalPort = msgObj.internalPort;
+
+        serverList.push(serverInfo);
+
+        console.log('Registered server: ' + serverInfo.serverName);
+    } else if (msgObj.cmd === 'requestIntroduction' ) {
+
+        var clientInfo = _.find(connections, { connectionId: msgObj.id });
+
+        if (!clientInfo) {
+            return;
+            console.error("No client found: " + msgObj.id);
+        }
+
+        var serverInfo = _.find(connections, { connectionId: msgObj.serverId });
+
+        if (!serverInfo) {
+            console.error("No client found: " + msgObj.id);
+            return;
+        }
+
+        // Send introduction request to server
+        var response = {
+            cmd: 'sendPacketToClient',
+            clientId: clientInfo.connectionId,
+            clientIP: clientInfo.externalIP,
+            clientPort: clientInfo.externalUDPPort
+        }
+
+        // Send this to the server
+        writeMessageToSocket(serverInfo.tcpSocket, response);
+    } else {
+        console.log('Unable to process message: ' + msg)
+    }
+}
+
+messageEventEmitter.addListener('msg', function(socket, message) {
+    try {
+        var msgObj = JSON.parse(message);
+        handleServerTCPMessage(socket, msgObj);
+    } catch (err) {
+        console.error(err);
+        console.error(err.stack);
+        console.log('Unable to parse JSON: ' + message)
+    }
+});
+
+function onDisconnect(sock) {
+    
+}
+
+console.log('Setting up tcp');
+var tcpServer = net.createServer();
+tcpServer.listen(MASTER_SERVER_PORT,'0.0.0.0');
+tcpServer.on('connection', function(sock) {
+    console.log('CONNECTED: ' + sock.remoteAddress +':'+ sock.remotePort);
+
+    // Set keep alive true so we can detect when a client or server has died
+    sock.setKeepAlive(true);
+
+
+    var readingLength = true;
+    var messageLengthStr = '';
+    var bytesRemainingInMasterServerMessage = 0;
+    var messageStr = '';
+
+    // Clean up on disconnect
+    sock.on('end', function() {
+       
+    });
+
+    sock.on('data', function (_data) {
+        var data = _data.toString();
+
+        var n=0;
+        var totalBytes = data.length;
+
+        while (n < totalBytes) {
+            var c = data[n++];
+
+            // Are we still reading in the length?
+            if (readingLength) {
+                if (c == ':') {
+                    bytesRemainingInMasterServerMessage = Number(messageLengthStr);
+                    readingLength = false;
+                }
+                else {
+                    messageLengthStr+=c;
+                }
+            }
+            else {
+                // Are we reading in the string?
+
+                messageStr+=c;
+                bytesRemainingInMasterServerMessage--;
+
+                // Did we hit the end of the string?
+                if (bytesRemainingInMasterServerMessage==0) {
+                    messageEventEmitter.emit('msg', sock, messageStr);
+                    readingLength = true;
+                    messageLengthStr='';
+                    messageStr='';
+                }
+            }
+        }
+    });
+
+    sock.on('error', function(error) {
+        console.log('Got TCP error: '+error);
+    });
+
+});

+ 15 - 0
Source/AtomicMasterServer/package.json

@@ -0,0 +1,15 @@
+{
+  "name": "atomic-master-server",
+  "version": "1.0.0",
+  "description": "",
+  "main": "app.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "lodash": "^4.11.1",
+    "uuid": "^2.0.2"
+  }
+}

+ 1 - 1
Source/ThirdParty/SDL/src/video/cocoa/SDL_cocoaevents.m

@@ -197,7 +197,7 @@ CreateApplicationMenus(void)
     if (NSApp == nil) {
         return;
     }
-    
+
     /* Create the main menu bar */
     [NSApp setMainMenu:[[NSMenu alloc] init]];
 

+ 10 - 0
Source/ThirdParty/kNet/include/kNet/Network.h

@@ -70,6 +70,11 @@ public:
 	/// Connects a raw socket (low-level, no MessageConnection abstraction) to the given destination.
 	Socket *ConnectSocket(const char *address, unsigned short port, SocketTransportLayer transport);
 
+	// BEGIN ATOMIC CHANGE
+	/// Create a raw socket (low-level, no MessageConnection abstraction) to the given destination.
+	Socket *CreateUnconnectedUDPSocket(const char *address, unsigned short port);
+	// END ATOMIC CHANGE
+
 	/// Frees the given Socket object (performs an immediate bidirectional shutdown and frees the socket). After calling 
 	/// this function, do not dereference that Socket pointer, as it is deleted.
 	void DeleteSocket(Socket *socket);
@@ -81,6 +86,11 @@ public:
 		free it by letting the refcount go to 0. */
 	Ptr(MessageConnection) Connect(const char *address, unsigned short port, SocketTransportLayer transport, IMessageHandler *messageHandler, Datagram *connectMessage = 0);
 
+	// BEGIN ATOMIC CHANGE
+	/** Connect with an existing socket. This is used when creating a connection with NAT punchthrough. */
+	Ptr(MessageConnection) Connect(Socket* s, IMessageHandler *messageHandler, Datagram *connectMessage = 0);
+	// END ATOMIC CHANGE
+
 	/// Returns the local host name of the system (the local machine name or the local IP, whatever is specified by the system).
 	const char *LocalAddress() const { return localHostName.c_str(); }
 

+ 4 - 1
Source/ThirdParty/kNet/include/kNet/Socket.h

@@ -85,7 +85,10 @@ enum SocketType
 	InvalidSocketType = 0, ///< A default invalid value for uninitialized sockets.
 	ServerListenSocket, ///< For TCP: a listen socket. For UDP: the single master socket handle that is used to send & receive all data.
 	ServerClientSocket, ///< For TCP: a client data socket. For UDP: a slave-mode Socket object that shares the underlying socket handle with the UDP master Socket.
-	ClientSocket ///< A client-side socket.
+	ClientSocket, ///< A client-side socket.
+	// BEGIN ATOMIC CHANGE
+	ClientConnectionLessSocket ///< A client-side socket without a connection
+	// END ATOMIC CHANGE
 };
 
 std::string SocketTypeToString(SocketType type);

+ 101 - 1
Source/ThirdParty/kNet/src/Network.cpp

@@ -715,7 +715,82 @@ Socket *Network::ConnectSocket(const char *address, unsigned short port, SocketT
 	return sock;
 }
 
-Ptr(MessageConnection) Network::Connect(const char *address, unsigned short port, 
+// BEGIN ATOMIC CHANGE
+// An unconnected UDP socket can be used to communicate to more than one host.
+// This is useful when using NAT punchthrough.
+Socket* Network::CreateUnconnectedUDPSocket(const char *address, unsigned short port)
+{
+	addrinfo *result = NULL;
+	addrinfo hints;
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_INET;
+	hints.ai_socktype = SOCK_DGRAM;
+	hints.ai_protocol = IPPROTO_UDP;
+
+	char strPort[256];
+	sprintf(strPort, "%d", (unsigned int)port);
+	int ret = getaddrinfo(address, strPort, &hints, &result);
+	if (ret != 0)
+	{
+		KNET_LOG(LogError, "Network::Connect: getaddrinfo failed: %s", GetErrorString(ret).c_str());
+		return 0;
+	}
+
+#ifdef WIN32
+	SOCKET connectSocket = WSASocket(result->ai_family, result->ai_socktype, result->ai_protocol,
+		NULL, 0, WSA_FLAG_OVERLAPPED);
+#else
+	SOCKET connectSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
+	KNET_LOG(LogInfo, "A call to socket() returned a new socket 0x%8X.", (unsigned int)connectSocket);
+#endif
+	if (connectSocket == INVALID_SOCKET)
+	{
+		KNET_LOG(LogError, "Network::Connect: Error at socket(): %s", GetLastErrorString().c_str());
+		freeaddrinfo(result);
+		return 0;
+	}
+
+	freeaddrinfo(result);
+
+	if (connectSocket == INVALID_SOCKET)
+	{
+		KNET_LOG(LogError, "Unable to connect to server!");
+		return 0;
+	}
+
+	EndPoint localEndPoint;
+	sockaddr_in sockname;
+	socklen_t socknamelen = sizeof(sockname);
+	ret = getsockname(connectSocket, (sockaddr*)&sockname, &socknamelen);
+	if (ret == 0)
+		localEndPoint = EndPoint::FromSockAddrIn(sockname);
+	else
+		KNET_LOG(LogError, "Network::ConnectSocket: getsockname failed: %s!", Network::GetLastErrorString().c_str());
+
+	EndPoint remoteEndPoint;
+	sscanf(address, "%hu.%hu.%hu.%hu",
+		   (unsigned short *) &remoteEndPoint.ip[0],
+		   (unsigned short *) &remoteEndPoint.ip[1],
+		   (unsigned short *) &remoteEndPoint.ip[2],
+		   (unsigned short *) &remoteEndPoint.ip[3]);
+
+	remoteEndPoint.port = port;
+
+	std::string remoteHostName = remoteEndPoint.IPToString();
+
+	const size_t maxSendSize = cMaxUDPSendSize;
+	Socket socket(connectSocket, localEndPoint, localHostName.c_str(), remoteEndPoint, remoteHostName.c_str(), SocketOverUDP, ServerClientSocket, maxSendSize);
+
+	socket.SetBlocking(false);
+	sockets.push_back(socket);
+
+	Socket *sock = &sockets.back();
+
+	return sock;
+}
+// END ATOMIC CHANGE
+
+Ptr(MessageConnection) Network::Connect(const char *address, unsigned short port,
 	SocketTransportLayer transport, IMessageHandler *messageHandler, Datagram *connectMessage)
 {
 	Socket *socket = ConnectSocket(address, port, transport);
@@ -743,6 +818,31 @@ Ptr(MessageConnection) Network::Connect(const char *address, unsigned short port
 	return connection;
 }
 
+// BEGIN ATOMIC CHANGE
+// Start up the connection worker thread on an existing socket.
+// This allows us to create a kNet connection with the same socket
+// we used to connect to the master server.
+Ptr(MessageConnection) Network::Connect(Socket* socket, IMessageHandler *messageHandler, Datagram *connectMessage)
+{
+	// This only works with UDP
+	if (socket->TransportLayer() == SocketOverTCP)
+	{
+		return 0;
+	}
+
+	SendUDPConnectDatagram(*socket, connectMessage);
+	KNET_LOG(LogInfo, "Network::Connect: Sent a UDP Connection Start datagram to to %s.", socket->ToString().c_str());
+
+	Ptr(MessageConnection) connection = new UDPMessageConnection(this, 0, socket, ConnectionPending);
+
+	connection->RegisterInboundMessageHandler(messageHandler);
+	AssignConnectionToWorkerThread(connection);
+
+	connections.insert(connection);
+	return connection;
+}
+// END ATOMIC CHANGE
+
 Socket *Network::CreateUDPSlaveSocket(Socket *serverListenSocket, const EndPoint &remoteEndPoint, const char *remoteHostName)
 {
 	if (!serverListenSocket)

+ 14 - 3
Source/ThirdParty/kNet/src/NetworkServer.cpp

@@ -330,10 +330,21 @@ bool NetworkServer::ProcessNewUDPConnectionAttempt(Socket *listenSocket, const E
 		Lockable<ConnectionMap>::LockType clientsLock = clients.Acquire();
 		if (clientsLock->find(endPoint) == clientsLock->end())
 			(*clientsLock)[endPoint] = connection;
-		else
-			KNET_LOG(LogError, "NetworkServer::ProcessNewUDPConnectionAttempt: Trying to overwrite an old connection with a new one! Discarding connection attempt datagram!",
-				timer.MSecsElapsed());
 
+		// BEGIN ATOMIC CHANGE
+		//else
+		//	KNET_LOG(LogError, "NetworkServer::ProcessNewUDPConnectionAttempt: Trying to overwrite an old connection with a new one! Discarding connection attempt datagram!",
+		//			 timer.MSecsElapsed());
+		else {
+			KNET_LOG(LogError,
+					 "NetworkServer::ProcessNewUDPConnectionAttempt: Trying to overwrite an old connection with a new one! Discarding connection attempt datagram!",
+					 timer.MSecsElapsed());
+
+			// If we do not return here, then the original connection will be lost. Since we are already connected,
+			// we do not want to lose the existing connection. This was causing issues when doing NAT punchthrough.
+			return false;
+		}
+		// END ATOMIC CHANGE
 
 		KNET_LOG(LogWaits, "NetworkServer::ProcessNewUDPConnectionAttempt: Accessing the connection list took %f msecs.",
 			timer.MSecsElapsed());

+ 8 - 2
Source/ThirdParty/kNet/src/NetworkWorkerThread.cpp

@@ -298,8 +298,14 @@ void NetworkWorkerThread::MainLoop()
 				} catch(const NetException &e)
 				{
 					KNET_LOG(LogError, (std::string("kNet::NetException thrown when processing client connection: ") + e.what()).c_str());
-					if (connection->GetSocket())
-						connection->GetSocket()->Close();
+					// BEGIN ATOMIC CHANGE
+					//Just ignore the exception and keep the socket open.
+					//Because we have to send dummy packets to open the ports on the routers,
+					//a non-valid packet may get through. We don't want to close the socket when
+					//this happens.
+					//if (connection->GetSocket())
+					//	connection->GetSocket()->Close();
+					// END ATOMIC CHANGE
 				}
 			}
 			else // A UDP server received a message.