diff --git a/include/Engine/Network/Client.h b/include/Engine/Network/Client.h index 83ec0535..54d83355 100644 --- a/include/Engine/Network/Client.h +++ b/include/Engine/Network/Client.h @@ -10,6 +10,7 @@ #include "Network/MessageType.h" #include "Network/NetworkDefines.h" +#include "Network/WinLeakCheck.h" #include "Core/EventBroker.h" #include "Core/EKeyDown.h" @@ -30,6 +31,7 @@ private: int Receive(char* data, size_t length); int CreateMessage(MessageType type, std::string message, char* data); + void Connect(); void Disconnect(); void MoveMessageHead(char*& data, size_t& length, size_t stepSize); void ParseMessageType(char* data, size_t length); diff --git a/include/Engine/Network/Server.h b/include/Engine/Network/Server.h index bb198b7e..964f251f 100644 --- a/include/Engine/Network/Server.h +++ b/include/Engine/Network/Server.h @@ -1,12 +1,58 @@ #ifndef Server_h__ #define Server_h__ +#include +#include -#include +#include +#include +#include +#include "NetworkDefines.h" +#include "MessageType.h" class Server { - Server(); - ~Server(); +public: + Server(); + ~Server(); + void Start(); + +private: + // udp stuff + boost::asio::ip::udp::endpoint m_ReceiverEndpoint; + boost::asio::io_service m_IOService; + boost::asio::ip::udp::socket m_Socket; + boost::asio::ip::udp::endpoint m_Connections[MAXCONNECTIONS]; + //Timers + std::clock_t m_StartPingTime; + std::clock_t m_StopTimes[8]; + // Game logic + std::string m_PlayerNames[MAXCONNECTIONS]; + char m_GameBoard[BOARDSIZE][BOARDSIZE]; + glm::vec2 m_PlayerPositions[8]; + + // Threaded + void DisplayLoop(); + void ReadFromClients(); + void InputLoop(); + + + int Receive(char* data, size_t length); + int CreateMessage(MessageType type, std::string message, char * data); + void MoveMessageHead(char*& data, size_t& length, size_t stepSize); + void Broadcast(std::string message); + void Broadcast(char* data, size_t length); + void SendSnapshot(); + void SendPing(); + void CheckForTimeOuts(); + int CreateHeader(MessageType type, char* data); + void Disconnect(int i); + void ParseMessageType(char* data, size_t length); + void ParseEvent(char* data, size_t length); + void ParseConnect(char* data, size_t length); + void ParseDisconnect(); + void ParseClientPing(); + void ParseServerPing(); + void ParseSnapshot(char* data, size_t length); }; #endif diff --git a/include/Engine/Network/WinLeakCheck.h b/include/Engine/Network/WinLeakCheck.h new file mode 100644 index 00000000..dc1216a7 --- /dev/null +++ b/include/Engine/Network/WinLeakCheck.h @@ -0,0 +1,17 @@ +#if defined (_WIN64) | defined(_WIN32) +#ifndef WinLeakeCheck_h__ +#define WinLeakeCheck_h__ + +//For memory leak checking +#define _CRTDBG_MAP_ALLOC +#include +#include + +#ifdef _DEBUG +#ifndef DBG_NEW +#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) +#define new DBG_NEW +#endif +#endif // _DEBUG +#endif +#endif \ No newline at end of file diff --git a/include/Game/Game.h b/include/Game/Game.h index bb1e3b49..2763b4dd 100644 --- a/include/Game/Game.h +++ b/include/Game/Game.h @@ -9,10 +9,13 @@ #include "GUI/Frame.h" #include "Core/World.h" #include "Rendering/RenderQueueFactory.h" + // Network #include +#include "Network/Server.h" #include "Network/Client.h" + class Game { public: @@ -33,8 +36,8 @@ private: RenderQueueFactory* m_RenderQueueFactory; // Network variables boost::thread m_NetworkThread; + Server m_Server; Client m_Client; - // Network methods void NetworkFunction(); // Network events diff --git a/src/Engine/Network/Client.cpp b/src/Engine/Network/Client.cpp index 602cdbe5..7ef2c210 100644 --- a/src/Engine/Network/Client.cpp +++ b/src/Engine/Network/Client.cpp @@ -15,7 +15,8 @@ Client::Client() : m_Socket(m_IOService) Client::~Client() { - + // will it work on linux? #if defined (_WIN64) | defined(_WIN32) otherwise. + _CrtDumpMemoryLeaks(); } void Client::Start(EventBroker* eventBroker) @@ -32,7 +33,6 @@ void Client::Start(EventBroker* eventBroker) } boost::thread_group threads; - socket_ptr sock(new udp::socket(m_IOService)); for (size_t i = 0; i < BOARDSIZE; i++) { for (size_t j = 0; j < BOARDSIZE; j++) { @@ -60,6 +60,7 @@ void Client::Close() { Disconnect(); m_ThreadIsRunning = false; + m_Socket.close(); } void Client::InputLoop() @@ -161,15 +162,16 @@ void Client::ParsePing() void Client::ParseServerPing() { - char* testMsg = new char[128]; - int testOffset = CreateMessage(MessageType::ServerPing, "Ping recieved", testMsg); + char* testMessage = new char[128]; + int testOffset = CreateMessage(MessageType::ServerPing, "Ping recieved", testMessage); //std::cout << "Parsing ping." << std::endl; m_Socket.send_to(boost::asio::buffer( - testMsg, + testMessage, testOffset), m_ReceiverEndpoint, 0); + delete[] testMessage; } void Client::ParseEventMessage(char* data, size_t length) @@ -257,6 +259,18 @@ int Client::CreateMessage(MessageType type, std::string message, char* data) return offset; } +void Client::Connect() +{ + char* dataPackage = new char[INPUTSIZE]; // The package that will be sent to the server, when filled + int length = CreateMessage(MessageType::Connect, m_PlayerName, dataPackage); + m_StartPingTime = std::clock(); + m_Socket.send_to(boost::asio::buffer( + dataPackage, + length), + m_ReceiverEndpoint, 0); + delete[] dataPackage; +} + void Client::Disconnect() { char* dataPackage = new char[INPUTSIZE]; // The package that will be sent to the server, when filled @@ -265,6 +279,7 @@ void Client::Disconnect() dataPackage, len), m_ReceiverEndpoint, 0); + delete[] dataPackage; } void Client::MoveMessageHead(char*& data, size_t& length, size_t stepSize) @@ -286,16 +301,11 @@ void Client::SendDebugInput() } if (GetAsyncKeyState('C')) { - int length = CreateMessage(MessageType::Connect, m_PlayerName, dataPackage); - m_StartPingTime = std::clock(); - m_Socket.send_to(boost::asio::buffer( - dataPackage, - length), - m_ReceiverEndpoint, 0); + Connect(); } - if (GetAsyncKeyState('Q')) { // Does not work. Plez fix - exit(1); + if (GetAsyncKeyState('Q')) { + Disconnect(); } memset(dataPackage, 0, INPUTSIZE); delete[] dataPackage; diff --git a/src/Engine/Network/Server.cpp b/src/Engine/Network/Server.cpp index 90c728a0..b66bd7b1 100644 --- a/src/Engine/Network/Server.cpp +++ b/src/Engine/Network/Server.cpp @@ -1,11 +1,395 @@ #include "Network/Server.h" -Server::Server() -{ - +Server::Server() : m_Socket(m_IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 13)) +{ } Server::~Server() { +} + + +void Server::Start() +{ + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + m_StopTimes[i] = std::clock(); + m_PlayerPositions[i].x = -1; + m_PlayerPositions[i].y = -1; + } + m_PlayerPositions[0].x = 0; + m_PlayerPositions[0].y = 0; + boost::thread_group threads; + + std::cout << "I am Server. BIP BOP\n"; + + threads.create_thread(boost::bind(&Server::DisplayLoop, this)); + threads.create_thread(boost::bind(&Server::ReadFromClients, this)); + threads.create_thread(boost::bind(&Server::InputLoop, this)); + + threads.join_all(); +} + +void Server::DisplayLoop() +{ + int lengthOfMsg = -1; + std::clock_t previousePingMessage = std::clock(); + std::clock_t previousSnapshotMessage = std::clock(); + std::clock_t timOutTimer = std::clock(); + int intervallMs = 1000; + int snapshotInterval = 50; + int timeToCheckTimeOutTime = 100; + char* data; + + for (;;) { + + std::clock_t currentTime = std::clock(); + // int tempTestRemovePlz = (1000 * (currentTime - previousSnapshotMessage) / (double)CLOCKS_PER_SEC); + // Send snapshot + if (snapshotInterval < (1000 * (currentTime - previousSnapshotMessage) / (double)CLOCKS_PER_SEC)) { + SendSnapshot(); + previousSnapshotMessage = currentTime; + } + + // Send pings each + if (intervallMs < (1000 * (currentTime - previousePingMessage) / (double)CLOCKS_PER_SEC)) { + SendPing(); + previousePingMessage = currentTime; + } + + // Time out logic + if (timeToCheckTimeOutTime < (1000 * (currentTime - timOutTimer) / (double)CLOCKS_PER_SEC)) { + CheckForTimeOuts(); + timOutTimer = currentTime; + } + } +} + +void Server::ReadFromClients() +{ + char readBuf[1024] = { 0 }; + int bytesRead = 0; + + for (;;) { + if (m_Socket.available()) { + try { + bytesRead = Receive(readBuf, INPUTSIZE); + ParseMessageType(readBuf, bytesRead); + } catch (const std::exception& err) { + // To not spam "socket closed messages" + //if (std::string(err.what()).find("forcefully closed") != std::string::npos) { + std::cout << "Read from client crashed: " << err.what(); + //} + } + } + } +} + +void Server::InputLoop() +{ + char inputBuffer[INPUTSIZE] = { 0 }; + std::string inputMessage; + + for (;;) { + std::cin.getline(inputBuffer, INPUTSIZE); + inputMessage = (std::string)inputBuffer; + + if (!inputMessage.empty()) { + try { + // Broadcast message typed in console + Broadcast(inputMessage); + + } catch (const std::exception& err) { + std::cout << "Read from WriteLoop crashed: " << err.what(); + } + } + if (inputMessage.find("exit") != std::string::npos) + exit(1); + inputMessage.clear(); + memset(inputBuffer, 0, INPUTSIZE); + } +} + +void Server::ParseMessageType(char * data, size_t length) +{ + int messageType = -1; + memcpy(&messageType, data, sizeof(int)); // Read what type off message was sent from server + MoveMessageHead(data, length, sizeof(int)); // Move the message head to know where to read from + + switch (static_cast(messageType)) { + case MessageType::Connect: + ParseConnect(data, length); + break; + case MessageType::ClientPing: + ParseClientPing(); + break; + case MessageType::ServerPing: + ParseServerPing(); + break; + case MessageType::Message: + break; + case MessageType::Snapshot: + ParseSnapshot(data, length); + break; + case MessageType::Disconnect: + ParseDisconnect(); + break; + case MessageType::Event: + ParseEvent(data, length); + break; + default: + break; + } +} + +int Server::Receive(char * data, size_t length) +{ + length = m_Socket.receive_from( + boost::asio::buffer((void*)data + , length) + , m_ReceiverEndpoint, 0); + return length; +} + +int Server::CreateMessage(MessageType type, std::string message, char * data) +{ + int lengthOfMessage = 0; + int offset = 0; + + lengthOfMessage = message.size(); + // Message type + memcpy(data + offset, &type, sizeof(int)); + offset += sizeof(int); + // Message, add one extra byte for null terminator + memcpy(data + offset, message.data(), (lengthOfMessage + 1) * sizeof(char)); + offset += (lengthOfMessage + 1) * sizeof(char); + + return offset; +} + +void Server::MoveMessageHead(char *& data, size_t & length, size_t stepSize) +{ + data += stepSize; + length -= stepSize; +} + +void Server::Broadcast(std::string message) +{ + std::cout << "Broadcast: " << message << std::endl; + char* data = new char[128]; + int offset = CreateMessage(MessageType::Event, message, data); + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_Connections[i].address() != boost::asio::ip::address()) { + m_Socket.send_to( + boost::asio::buffer(data, offset), + m_Connections[i], + 0); + } + } + delete[] data; +} + +void Server::Broadcast(char * data, size_t length) +{ + for (int i = 0; i < MAXCONNECTIONS; ++i) { + if (m_Connections[i].address() != boost::asio::ip::address()) { + m_Socket.send_to( + boost::asio::buffer(data, length), + m_Connections[i], + 0); + } + } +} + +void Server::SendSnapshot() +{ + char* data = new char[128]; + int offset = CreateHeader(MessageType::Snapshot, data); + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + memcpy(data + offset, &m_PlayerPositions[i].x, sizeof(float)); + offset += sizeof(float); + memcpy(data + offset, &m_PlayerPositions[i].y, sizeof(float)); + offset += sizeof(float); + // +1 for null terminator + memcpy(data + offset, m_PlayerNames[i].data(), m_PlayerNames[i].size() + 1); + offset += (m_PlayerNames[i].size() + 1) * sizeof(char); + } + Broadcast(data, offset); + delete[] data; +} + +void Server::SendPing() +{ + // Prints connected players ping + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + if (m_Connections[i].address() != boost::asio::ip::address()) + std::cout << "Player " << i << "'s ping: " << 1000 * (m_StopTimes[i] - m_StartPingTime) + / static_cast(CLOCKS_PER_SEC) << std::endl; + } + + // Create ping message + char* data = new char[128]; + int len = CreateMessage(MessageType::ServerPing, "Ping from server", data); + // Time message + m_StartPingTime = std::clock(); + // Send message + Broadcast(data, len); + delete[] data; } + +void Server::CheckForTimeOuts() +{ + int timeOutTimeMs = 5000; + + int tempStartPing = 1000 * m_StartPingTime + / static_cast(CLOCKS_PER_SEC); + + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + if (m_Connections[i].address() != boost::asio::ip::address()) { + int tempStopPing = 1000 * m_StopTimes[i] + / static_cast(CLOCKS_PER_SEC); + if (tempStartPing > tempStopPing + timeOutTimeMs) { + std::cout << "player " << i << " timed out!" << std::endl; + Disconnect(i); + } + + } + } +} + +int Server::CreateHeader(MessageType type, char * data) +{ + int messageType = static_cast(type); + int offset = 0; + memcpy(data, &messageType, sizeof(int)); + offset += sizeof(int); + + return offset; +} + +void Server::Disconnect(int i) +{ + Broadcast("A player disconnected"); + std::cout << "Player " << i << " disconnected/Timed out" << std::endl; + m_Connections[i] = boost::asio::ip::udp::endpoint(); + // Reset disconnected players position + m_PlayerPositions[i].x = -1; + m_PlayerPositions[i].y = -1; +} + +void Server::ParseEvent(char * data, size_t length) +{ + size_t i; + for (i = 0; i < MAXCONNECTIONS; i++) { + if (m_Connections[i].address() == m_ReceiverEndpoint.address()) { + break; + } + } + + if ("+Forward" == std::string(data)) + if (m_PlayerPositions[i].y > 0) + m_PlayerPositions[i].y--; + if ("-Forward" == std::string(data)) + if (m_PlayerPositions[i].y < BOARDSIZE - 1) + m_PlayerPositions[i].y++; + if ("+Right" == std::string(data)) + if (m_PlayerPositions[i].x < BOARDSIZE - 1) + m_PlayerPositions[i].x++; + if ("-Right" == std::string(data)) + if (m_PlayerPositions[i].x > 0) + m_PlayerPositions[i].x--; +} + +void Server::ParseConnect(char * data, size_t length) +{ + std::cout << "Parsing connection." << std::endl; + + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_Connections[i].address() == m_ReceiverEndpoint.address()) { + return; + } + } + + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_Connections[i].address() == boost::asio::ip::address()) { + m_Connections[i] = m_ReceiverEndpoint; + + m_PlayerNames[i] = std::string(data); + m_PlayerPositions[i].x = 0; + m_PlayerPositions[i].y = 0; + m_StopTimes[i] = std::clock(); + std::cout << "Player \"" << m_PlayerNames[i] << "\" connected on IP: " << m_Connections[i].address().to_string() << std::endl; + + int offset = 0; + char* temp = new char[sizeof(int) * 2]; + int msgType = 0; + memcpy(temp, &msgType, sizeof(int)); + offset += sizeof(int); + memcpy(temp + offset, &i, sizeof(int)); + + m_Socket.send_to( + boost::asio::buffer(temp, sizeof(int) * 2), + m_Connections[i], + 0); + // Send notification that a player has connected + std::string str = "Player " + m_PlayerNames[i] + " connected on: " + m_ReceiverEndpoint.address().to_string(); + Broadcast(str); + // +1 is the null terminator + MoveMessageHead(data, length, m_PlayerNames[i].size() + 1); + delete[] temp; + break; + } + } +} + +void Server::ParseDisconnect() +{ + std::cout << "Parsing disconnect. \n"; + + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_Connections[i].address() == m_ReceiverEndpoint.address()) { + Disconnect(i); + break; + } + } +} + +void Server::ParseClientPing() +{ + char* testMesssage = new char[128]; + int testOffset = CreateMessage(MessageType::ClientPing, "Ping recieved", testMesssage); + + std::cout << "Parsing ping." << std::endl; + // Return ping + m_Socket.send_to( + boost::asio::buffer( + testMesssage, + testOffset), + m_ReceiverEndpoint, + 0); + delete[] testMesssage; +} + +void Server::ParseServerPing() +{ + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_Connections[i].address() == m_ReceiverEndpoint.address()) { + m_StopTimes[i] = std::clock(); + break; + } + } +} + +void Server::ParseSnapshot(char * data, size_t length) +{ + // Does no logic. Returns snapshot if client request one + // The snapshot is not a real snapshot tho... + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_Connections[i].address() != boost::asio::ip::address()) { + m_Socket.send_to( + boost::asio::buffer("I'm sending a snapshot to you guys!"), + m_Connections[i], + 0); + } + } +} \ No newline at end of file diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index 9c523c4b..53f591ba 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -82,4 +82,7 @@ void Game::NetworkFunction() if (inputMessage == "c" || inputMessage == "C") { m_Client.Start(m_EventBroker); } + if (inputMessage == "s" || inputMessage == "S") { + m_Server.Start(); + } } \ No newline at end of file