Basic network classes added: Client, Server, ClientApplication, ServerApplication.

This commit is contained in:
Quentin Bazin 2019-01-09 20:44:58 +01:00
parent c36481f4ef
commit 3884be8868
34 changed files with 688 additions and 16 deletions

View File

@ -1,18 +1,18 @@
/*
* =====================================================================================
*
* Filename: Application.hpp
* Filename: ClientApplication.hpp
*
* Description:
*
* Created: 14/12/2014 05:09:11
* Created: 09/01/2019 19:33:30
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#ifndef APPLICATION_HPP_
#define APPLICATION_HPP_
#ifndef CLIENTAPPLICATION_HPP_
#define CLIENTAPPLICATION_HPP_
#include <gk/core/CoreApplication.hpp>
@ -20,9 +20,9 @@
#include "Registry.hpp"
#include "ScriptEngine.hpp"
class Application : public gk::CoreApplication {
class ClientApplication : public gk::CoreApplication {
public:
Application(int argc, char **argv) : gk::CoreApplication(argc, argv) {}
ClientApplication(int argc, char **argv) : gk::CoreApplication(argc, argv) {}
void init() override;
@ -36,4 +36,4 @@ class Application : public gk::CoreApplication {
Registry m_registry;
};
#endif // APPLICATION_HPP_
#endif // CLIENTAPPLICATION_HPP_

View File

@ -0,0 +1,52 @@
/*
* =====================================================================================
*
* Filename: Client.hpp
*
* Description:
*
* Created: 25/01/2018 12:26:01
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#ifndef CLIENT_HPP_
#define CLIENT_HPP_
#include <SFML/Network/TcpSocket.hpp>
#include <SFML/Network/UdpSocket.hpp>
#include <gk/core/ApplicationStateStack.hpp>
#include <gk/core/Timer.hpp>
class Client {
public:
void connect(sf::IpAddress serverAddress, u16 serverPort);
void disconnect();
void sendReady();
void sendKeyState();
void update(bool &hasGameStarted);
bool isConnected() const { return m_isConnected; }
u16 id() const { return m_id; }
private:
bool m_isConnected = false;
u16 m_id;
sf::IpAddress m_serverAddress;
u16 m_serverPort;
std::unique_ptr<sf::TcpSocket> m_tcpSocket;
sf::UdpSocket m_socket;
gk::Timer m_keyUpdateTimer;
};
#endif // CLIENT_HPP_

View File

@ -19,6 +19,7 @@
#include <gk/core/ApplicationState.hpp>
#include <gk/gl/RenderTarget.hpp>
#include "Client.hpp"
#include "Config.hpp"
#include "HUD.hpp"
#include "LuaCore.hpp"
@ -52,6 +53,8 @@ class GameState : public gk::ApplicationState {
HUD m_hud{m_player, m_world};
LuaCore m_luaCore;
Client m_client;
};
#endif // GAMESTATE_HPP_

View File

@ -1,11 +1,11 @@
/*
* =====================================================================================
*
* Filename: Application.cpp
* Filename: ClientApplication.cpp
*
* Description:
*
* Created: 14/12/2014 05:09:21
* Created: 09/01/2019 19:33:52
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
@ -15,12 +15,12 @@
#include <gk/core/Mouse.hpp>
#include <gk/graphics/Font.hpp>
#include "Application.hpp"
#include "ClientApplication.hpp"
#include "Config.hpp"
#include "GameState.hpp"
#include "TextureLoader.hpp"
void Application::init() {
void ClientApplication::init() {
gk::CoreApplication::init();
gk::GamePad::init(m_keyboardHandler);
@ -44,7 +44,7 @@ void Application::init() {
m_stateStack.push<GameState>();
}
void Application::initOpenGL() {
void ClientApplication::initOpenGL() {
// Enable transparency
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

View File

@ -11,10 +11,10 @@
*
* =====================================================================================
*/
#include "Application.hpp"
#include "ClientApplication.hpp"
int main(int argc, char **argv) {
Application app(argc, argv);
ClientApplication app(argc, argv);
app.run();
return 0;

View File

@ -0,0 +1,112 @@
/*
* =====================================================================================
*
* Filename: Client.cpp
*
* Description:
*
* Created: 25/01/2018 12:26:43
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#include <gk/core/input/GamePad.hpp>
#include <gk/core/input/InputHandler.hpp>
#include <gk/core/GameClock.hpp>
#include <gk/core/Exception.hpp>
#include "Client.hpp"
#include "Network.hpp"
void Client::connect(sf::IpAddress serverAddress, u16 serverPort) {
m_serverAddress = serverAddress;
m_serverPort = serverPort;
m_tcpSocket.reset(new sf::TcpSocket);
if (serverAddress.toInteger() == 0 || m_tcpSocket->connect(serverAddress, serverPort, sf::seconds(5)) != sf::Socket::Done)
throw EXCEPTION("Network error: Unable to connect to server", serverAddress.toString() + ":" + std::to_string(serverPort));
if (m_socket.bind(0) != sf::Socket::Done)
throw EXCEPTION("Network error: Bind failed");
sf::Packet packet;
packet << Network::Command::ClientConnect << sf::IpAddress::getLocalAddress().toString() << m_socket.getLocalPort();
m_tcpSocket->send(packet);
sf::Packet answer;
m_tcpSocket->receive(answer);
Network::Command command;
answer >> command;
if (command == Network::Command::ClientRefused)
throw EXCEPTION("Server error: You can't join an already started game.");
if (command != Network::Command::ClientOk)
throw EXCEPTION("Network error: Expected 'ClientOk' packet.");
answer >> m_id;
m_tcpSocket->setBlocking(false);
m_socket.setBlocking(false);
m_isConnected = true;
}
void Client::disconnect() {
sf::Packet packet;
packet << Network::Command::ClientDisconnect;
m_tcpSocket->send(packet);
}
void Client::sendReady() {
sf::Packet packet;
packet << Network::Command::ClientReady << m_id;
m_tcpSocket->send(packet);
}
void Client::sendKeyState() {
if (!m_keyUpdateTimer.isStarted())
m_keyUpdateTimer.start();
if (m_keyUpdateTimer.time() > 15) {
gk::InputHandler *inputHandler = gk::GamePad::getInputHandler();
if (inputHandler) {
sf::Packet packet;
packet << Network::Command::KeyState << gk::GameClock::getTicks() << m_id;
for (auto &it : inputHandler->keysPressed()) {
packet << static_cast<u8>(it.first) << it.second;
}
m_socket.send(packet, m_serverAddress, m_serverPort);
}
m_keyUpdateTimer.reset();
m_keyUpdateTimer.start();
}
}
void Client::update(bool &hasGameStarted) {
sf::Packet packet;
sf::IpAddress senderAddress;
u16 senderPort;
while (m_socket.receive(packet, senderAddress, senderPort) == sf::Socket::Done) {
Network::Command command;
packet >> command;
std::cout << "UDP Message of type '" << Network::commandToString(command) << "' received from: " << senderAddress << ":" << senderPort << std::endl;
}
while (m_tcpSocket->receive(packet) == sf::Socket::Done) {
Network::Command command;
packet >> command;
if (command == Network::Command::GameStart) {
hasGameStarted = true;
}
else if (command == Network::Command::GameEnd) {
disconnect();
break;
}
}
}

View File

@ -18,6 +18,7 @@
#include <gk/core/ApplicationStateStack.hpp>
#include <gk/core/input/GamePad.hpp>
#include <gk/core/Exception.hpp>
#include <gk/core/Mouse.hpp>
#include <gk/gl/OpenGL.hpp>
#include <gk/core/GameClock.hpp>
@ -30,6 +31,13 @@
#include "ScriptEngine.hpp"
GameState::GameState() {
try {
m_client.connect("localhost", 4242);
}
catch (const gk::Exception &e) {
std::cerr << "Error " << e.what() << std::endl;
}
m_camera.setAspectRatio((float)SCREEN_WIDTH / SCREEN_HEIGHT);
World::setInstance(m_world);

View File

@ -0,0 +1,42 @@
/*
* =====================================================================================
*
* Filename: Network.hpp
*
* Description:
*
* Created: 22/01/2018 14:11:41
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#ifndef NETWORK_HPP_
#define NETWORK_HPP_
#include <SFML/Network/Packet.hpp>
namespace Network {
enum class Command {
// Client commands
ClientConnect, // <TCP> [NetworkCommand][u16 udp port]
ClientOk, // <TCP> [NetworkCommand][u16 client id]
ClientRefused, // <TCP> [NetworkCommand]
ClientReady, // <TCP> [NetworkCommand][u16 client id]
ClientDisconnect, // <TCP> [NetworkCommand]
// Game commands
GameStart, // <TCP> [NetworkCommand]
GameEnd, // <TCP> [NetworkCommand]
// Input commands
KeyState, // <UDP> [NetworkCommand][u32 timestamp][u16 client id][u32 keycode][bool isPressed]...
};
std::string commandToString(Command command);
}
sf::Packet &operator<<(sf::Packet &packet, Network::Command command);
sf::Packet &operator>>(sf::Packet &packet, Network::Command &command);
#endif // NETWORK_HPP_

View File

@ -0,0 +1,44 @@
/*
* =====================================================================================
*
* Filename: Network.cpp
*
* Description:
*
* Created: 22/01/2018 14:12:53
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#include <map>
#include <gk/core/IntTypes.hpp>
#include "Network.hpp"
std::string Network::commandToString(Network::Command command) {
std::map<Network::Command, std::string> commandNames = {
{Network::Command::ClientConnect, "ClientConnect"},
{Network::Command::ClientOk, "ClientOk"},
{Network::Command::ClientRefused, "ClientRefused"},
{Network::Command::ClientReady, "ClientReady"},
{Network::Command::ClientDisconnect, "ClientDisconnect"},
{Network::Command::GameStart, "GameStart"},
{Network::Command::GameEnd, "GameEnd"},
{Network::Command::KeyState, "KeyState"},
};
return commandNames[command];
}
sf::Packet &operator<<(sf::Packet &packet, Network::Command command) {
return packet << static_cast<u16>(command);
}
sf::Packet &operator>>(sf::Packet &packet, Network::Command &command) {
u16 tmp;
packet >> tmp;
command = static_cast<Network::Command>(tmp);
return packet;
}

View File

@ -0,0 +1,26 @@
/*
* =====================================================================================
*
* Filename: NetworkInputHandler.hpp
*
* Description:
*
* Created: 21/01/2018 10:54:01
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#ifndef NETWORKINPUTHANDLER_HPP_
#define NETWORKINPUTHANDLER_HPP_
#include <gk/core/input/InputHandler.hpp>
class NetworkInputHandler : public gk::InputHandler {
public:
void setKeyPressed(gk::GameKey key, bool isPressed) {
m_keysPressed[key] = isPressed;
}
};
#endif // NETWORKINPUTHANDLER_HPP_

View File

@ -0,0 +1,37 @@
/*
* =====================================================================================
*
* Filename: ServerApplication.hpp
*
* Description:
*
* Created: 09/01/2019 19:37:20
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#ifndef SERVERAPPLICATION_HPP_
#define SERVERAPPLICATION_HPP_
#include <gk/core/CoreApplication.hpp>
#include "NetworkInputHandler.hpp"
#include "Registry.hpp"
#include "Server.hpp"
class ServerApplication : public gk::CoreApplication {
public:
ServerApplication(int argc, char **argv) : gk::CoreApplication(argc, argv) {}
void init() override;
private:
void mainLoop() override;
Server m_server;
Registry m_registry;
};
#endif // SERVERAPPLICATION_HPP_

View File

@ -0,0 +1,56 @@
/*
* =====================================================================================
*
* Filename: Server.hpp
*
* Description:
*
* Created: 23/01/2018 14:47:14
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#ifndef SERVER_HPP_
#define SERVER_HPP_
#include <SFML/Network/SocketSelector.hpp>
#include <SFML/Network/TcpListener.hpp>
#include <SFML/Network/UdpSocket.hpp>
#include "ServerInfo.hpp"
class Server {
public:
void init();
void handleKeyState();
void handleGameEvents();
bool isRunning() const { return m_isRunning; }
bool hasGameStarted() const { return m_hasGameStarted; }
ServerInfo &info() { return m_info; }
sf::UdpSocket &udpSocket() { return m_udpSocket; }
void setRunning(bool isRunning) { m_isRunning = isRunning; }
void setGameStarted(bool hasGameStarted) { m_hasGameStarted = hasGameStarted; }
private:
void handleNewConnections();
void handleClientMessages();
bool m_isRunning = true;
bool m_hasGameStarted = false;
ServerInfo m_info;
sf::UdpSocket m_udpSocket;
sf::TcpListener m_tcpListener;
sf::SocketSelector m_selector;
};
#endif // SERVER_HPP_

View File

@ -0,0 +1,57 @@
/*
* =====================================================================================
*
* Filename: ServerInfo.hpp
*
* Description:
*
* Created: 22/01/2018 19:03:23
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#ifndef SERVERINFO_HPP_
#define SERVERINFO_HPP_
#include <algorithm>
#include <memory>
#include <vector>
#include <SFML/Network.hpp>
#include <gk/core/Exception.hpp>
#include "NetworkInputHandler.hpp"
struct Client {
Client(u16 _id, sf::IpAddress _address, u16 _port, const std::shared_ptr<sf::TcpSocket> &socket)
: id(_id), address(_address), port(_port), tcpSocket(socket) {}
u16 id;
bool isReady = false;
sf::IpAddress address;
u16 port;
u32 previousKeyTimestamp = 0;
std::shared_ptr<sf::TcpSocket> tcpSocket;
NetworkInputHandler inputHandler;
};
class ServerInfo {
public:
Client &addClient(sf::IpAddress address, u16 port, const std::shared_ptr<sf::TcpSocket> &socket);
Client *getClient(u16 id);
void removeClient(u16 id);
std::vector<Client> &clients() { return m_clients; }
const std::vector<Client> &clients() const { return m_clients; }
private:
std::vector<Client> m_clients;
};
#endif // SERVERINFO_HPP_

View File

@ -0,0 +1,44 @@
/*
* =====================================================================================
*
* Filename: ServerApplication.cpp
*
* Description:
*
* Created: 09/01/2019 19:41:15
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#include "ServerApplication.hpp"
void ServerApplication::init() {
m_server.init();
m_server.setRunning(true);
m_server.setGameStarted(false);
gk::CoreApplication::init();
Registry::setInstance(m_registry);
// m_scriptEngine.init();
}
void ServerApplication::mainLoop() {
while (m_server.isRunning()) {
handleEvents();
m_server.handleGameEvents();
if (m_server.hasGameStarted()) {
m_server.handleKeyState();
m_clock.updateGame([&] {
});
m_clock.waitForNextFrame();
}
}
}

View File

@ -11,10 +11,10 @@
*
* =====================================================================================
*/
#include "Application.hpp"
#include "ServerApplication.hpp"
int main(int argc, char **argv) {
Application app(argc, argv);
ServerApplication app(argc, argv);
app.run();
return 0;

View File

@ -0,0 +1,157 @@
/*
* =====================================================================================
*
* Filename: Server.cpp
*
* Description:
*
* Created: 23/01/2018 14:48:07
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#include "Network.hpp"
#include "Server.hpp"
void Server::init() {
if (m_udpSocket.bind(4242) != sf::Socket::Done)
throw EXCEPTION("Network error: Bind failed");
m_udpSocket.setBlocking(false);
if (m_tcpListener.listen(4242) != sf::Socket::Done)
throw EXCEPTION("Network error: Listen failed");
m_tcpListener.setBlocking(false);
m_selector.add(m_tcpListener);
}
void Server::handleKeyState() {
sf::Packet packet;
sf::IpAddress senderAddress;
u16 senderPort;
while (m_udpSocket.receive(packet, senderAddress, senderPort) == sf::Socket::Status::Done) {
Network::Command command;
u32 timestamp;
u16 clientId;
packet >> command >> timestamp >> clientId;
std::cout << "UDP Message of type '" << Network::commandToString(command) << "' received from: " << senderAddress << ":" << senderPort << std::endl;
if (command == Network::Command::KeyState) {
Client *client = m_info.getClient(clientId);
if (client && client->previousKeyTimestamp < timestamp) {
client->previousKeyTimestamp = timestamp;
while (!packet.endOfPacket()) {
u8 key;
bool isPressed;
packet >> key >> isPressed;
client->inputHandler.setKeyPressed(key, isPressed);
}
}
}
}
}
void Server::handleGameEvents() {
if (m_selector.wait(sf::milliseconds(10))) {
if (m_selector.isReady(m_tcpListener)) {
handleNewConnections();
}
else {
handleClientMessages();
}
}
}
void Server::handleNewConnections() {
std::shared_ptr<sf::TcpSocket> clientSocket = std::make_shared<sf::TcpSocket>();
if (m_tcpListener.accept(*clientSocket) == sf::Socket::Done) {
sf::Packet packet;
clientSocket->receive(packet);
Network::Command command;
packet >> command;
if (command != Network::Command::ClientConnect)
throw EXCEPTION("Network error: Expected 'ClientConnect' packet.");
if (!m_hasGameStarted && m_info.clients().size() < 5) {
std::string address;
u16 port;
packet >> address >> port;
Client &client = m_info.addClient(address, port, clientSocket);
// FIXME
// scene.addObject(PlayerFactory::create(20, 50 + 100 * client.id, client.id));
m_selector.add(*client.tcpSocket);
sf::Packet outPacket;
outPacket << Network::Command::ClientOk << client.id;
client.tcpSocket->send(outPacket);
client.tcpSocket->setBlocking(false);
}
else {
sf::Packet outPacket;
outPacket << Network::Command::ClientRefused;
clientSocket->send(outPacket);
}
}
else {
std::cerr << "Warning: Connection accept failed." << std::endl;
}
}
// FIXME: Refactor this function
void Server::handleClientMessages() {
bool areAllClientsReady = true;
for (size_t i = 0 ; i < m_info.clients().size() ; ++i) {
Client &client = m_info.clients()[i];
if (m_selector.isReady(*client.tcpSocket)) {
sf::Packet packet;
if (client.tcpSocket->receive(packet) == sf::Socket::Done) {
Network::Command command;
packet >> command;
if (command == Network::Command::ClientDisconnect) {
// FIXME
// sf::Packet packet;
// packet << Network::Command::EntityDie << "Player" + std::to_string(client.id);
// for (Client &client : m_info.clients()) {
// client.tcpSocket->send(packet);
// }
--i;
m_selector.remove(*client.tcpSocket);
m_info.removeClient(client.id);
if (m_info.clients().size() == 0) {
// m_tcpListener.close();
m_isRunning = false;
m_hasGameStarted = false;
}
}
else if (command == Network::Command::ClientReady) {
client.isReady = true;
}
}
}
if (!client.isReady)
areAllClientsReady = false;
}
if (areAllClientsReady) {
sf::Packet packet;
packet << Network::Command::GameStart;
for (Client &client : m_info.clients()) {
client.tcpSocket->send(packet);
}
m_hasGameStarted = true;
}
}

View File

@ -0,0 +1,34 @@
/*
* =====================================================================================
*
* Filename: ServerInfo.cpp
*
* Description:
*
* Created: 26/01/2018 22:41:02
*
* Author: Quentin Bazin, <quent42340@gmail.com>
*
* =====================================================================================
*/
#include "ServerInfo.hpp"
Client &ServerInfo::addClient(sf::IpAddress address, u16 port, const std::shared_ptr<sf::TcpSocket> &socket) {
m_clients.emplace_back(m_clients.size(), address, port, socket);
return m_clients.back();
}
Client *ServerInfo::getClient(u16 id) {
auto it = std::find_if(m_clients.begin(), m_clients.end(), [id] (Client &client) { return client.id == id; });
if (it == m_clients.end())
return nullptr;
return &*it;
}
void ServerInfo::removeClient(u16 id) {
auto it = std::find_if(m_clients.begin(), m_clients.end(), [id] (Client &client) { return client.id == id; });
if (it != m_clients.end())
m_clients.erase(it);
}