diff --git a/.gitignore b/.gitignore index 1c78b71..36a66e5 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,9 @@ GhostServer/Debug/ GhostServer/Release/ .vs/GhostServer/v16/.suo + +# Linux build artifacts +GhostServer/ui_mainwindow.h +GhostServer/*_qt.cpp +config.mk +ghost_server diff --git a/GhostServer/networkmanager.cpp b/GhostServer/networkmanager.cpp index 8e8adfe..b15a4fe 100644 --- a/GhostServer/networkmanager.cpp +++ b/GhostServer/networkmanager.cpp @@ -1,11 +1,18 @@ #include "networkmanager.h" #include +#include +#include +#include #include #include +#define HEARTBEAT_RATE 1000 + +static std::chrono::time_point lastHeartbeat; + //DataGhost sf::Packet& operator>>(sf::Packet& packet, Vector& vec) @@ -97,8 +104,8 @@ void NetworkManager::StopServer() return; } - for (auto& client : this->clients) { - this->DisconnectPlayer(client); + while (this->clients.size() > 0) { + this->DisconnectPlayer(this->clients.back()); } this->isRunning = false; @@ -110,8 +117,9 @@ void NetworkManager::StopServer() void NetworkManager::DisconnectPlayer(Client& c) { sf::Packet packet; - packet << c.IP.toInteger() << HEADER::DISCONNECT; + packet << HEADER::DISCONNECT << c.ID; int id = 0; + int toErase = -1; for (; id < this->clients.size(); ++id) { if (this->clients[id].IP != c.IP) { this->clients[id].tcpSocket->send(packet); @@ -119,9 +127,13 @@ void NetworkManager::DisconnectPlayer(Client& c) emit this->OnNewEvent("Player " + QString::fromStdString(this->clients[id].name) + " has disconnected !"); this->selector.remove(*this->clients[id].tcpSocket); this->clients[id].tcpSocket->disconnect(); - this->clients.erase(this->clients.begin() + id); + toErase = id; } } + + if (toErase != -1) { + this->clients.erase(this->clients.begin() + toErase); + } } void NetworkManager::DisconnectPlayer(sf::Uint32 ID) @@ -196,6 +208,7 @@ void NetworkManager::CheckConnection() client.modelName = model_name; client.currentMap = level_name; client.TCP_only = TCP_only; + client.returnedHeartbeat = true; // Make sure they don't get immediately disconnected; their heartbeat starts on next beat this->selector.add(*client.tcpSocket); @@ -292,13 +305,24 @@ void NetworkManager::TreatTCP(sf::Packet& packet) if (client) { std::string map; packet >> map; + client->currentMap = map; emit this->OnNewEvent(QString::fromStdString(client->name) + " is now on " + QString::fromStdString(map)); } break; } - case HEADER::HEART_BEAT: - break; + case HEADER::HEART_BEAT: { + auto client = this->GetClientByID(ID); + if (client) { + uint32_t token; + packet >> token; + if (token == client->heartbeatToken) { + // Good heartbeat! + client->returnedHeartbeat = true; + } + break; + } + } case HEADER::MESSAGE: { for (auto& client : this->clients) { client.tcpSocket->send(packet); @@ -321,8 +345,14 @@ void NetworkManager::TreatTCP(sf::Packet& packet) break; } case HEADER::MODEL_CHANGE: { - for (auto& client : this->clients) { - client.tcpSocket->send(packet); + std::string modelName; + packet >> modelName; + auto client = this->GetClientByID(ID); + if (client) { + client->modelName = modelName; + for (auto& other : this->clients) { + other.tcpSocket->send(packet); + } } break; } @@ -350,6 +380,11 @@ void NetworkManager::RunServer() this->clock.restart(); while (this->isRunning) { + auto now = std::chrono::steady_clock::now(); + if (now > lastHeartbeat + std::chrono::milliseconds(HEARTBEAT_RATE)) { + this->DoHeartbeats(); + lastHeartbeat = now; + } //UDP std::vector buffer; @@ -360,20 +395,55 @@ void NetworkManager::RunServer() if (this->selector.isReady(this->listener)) { this->CheckConnection(); //A player wants to connect } else { + std::queue toDisconnect; + for (int i = 0; i < this->clients.size(); ++i) { if (this->selector.isReady(*this->clients[i].tcpSocket)) { sf::Packet packet; sf::Socket::Status status = this->clients[i].tcpSocket->receive(packet); if (status == sf::Socket::Disconnected) { - this->DisconnectPlayer(this->clients[i]); + toDisconnect.push(&this->clients[i]); continue; } this->TreatTCP(packet); } } + + while (!toDisconnect.empty()) { + this->DisconnectPlayer(*toDisconnect.front()); + toDisconnect.pop(); + } } } } this->StopServer(); } + +void NetworkManager::DoHeartbeats() +{ + // We don't disconnect clients in the loop; else, the loop will have + // UB + std::queue toDisconnect; + + for (auto& client : this->clients) { + if (!client.returnedHeartbeat) { + // Client didn't return heartbeat in time; sever connection + toDisconnect.push(&client); + } else { + // Send a heartbeat + client.heartbeatToken = rand(); + client.returnedHeartbeat = false; + sf::Packet packet; + packet << HEADER::HEART_BEAT << sf::Uint32(client.ID) << sf::Uint32(client.heartbeatToken); + if (client.tcpSocket->send(packet) == sf::Socket::Disconnected) { + toDisconnect.push(&client); + } + } + } + + while (!toDisconnect.empty()) { + this->DisconnectPlayer(*toDisconnect.front()); + toDisconnect.pop(); + } +} diff --git a/GhostServer/networkmanager.h b/GhostServer/networkmanager.h index 10398ba..3feed81 100644 --- a/GhostServer/networkmanager.h +++ b/GhostServer/networkmanager.h @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -46,6 +47,8 @@ struct Client { std::string currentMap; std::unique_ptr tcpSocket; bool TCP_only; + uint32_t heartbeatToken; + bool returnedHeartbeat; }; class NetworkManager : public QObject @@ -70,6 +73,8 @@ class NetworkManager : public QObject sf::Clock clock; + void DoHeartbeats(); + public: sf::UdpSocket udpSocket; NetworkManager(); diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2ca4a84 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +.PHONY: all clean + +CXX=clang++ +SDIR=GhostServer +ODIR=obj + +SRCS=$(SDIR)/main.cpp $(SDIR)/mainwindow.cpp $(SDIR)/networkmanager.cpp $(SDIR)/mainwindow_qt.cpp $(SDIR)/networkmanager_qt.cpp + +OBJS=$(patsubst $(SDIR)/%.cpp, $(ODIR)/%.o, $(SRCS)) + +DEPS=$(OBJS:%.o=%.d) + +CXXFLAGS=-std=c++17 -fPIC +LDFLAGS=-lpthread + +include config.mk + +all: ghost_server +clean: + rm -rf $(ODIR) ghost_server + rm -rf $(SDIR)/ui_mainwindow.h $(SDIR)/mainwindow_qt.cpp $(SDIR)/networkmanager_qt.cpp + +-include $(DEPS) + +ghost_server: $(SDIR)/ui_mainwindow.h $(OBJS) + $(CXX) $(LDFLAGS) $(OBJS) -o $@ + +$(ODIR)/%.o: $(SDIR)/%.cpp + @mkdir -p $(dir $@) + $(CXX) $(CXXFLAGS) -MMD -c $< -o $@ + +$(SDIR)/ui_mainwindow.h: $(SDIR)/mainwindow.ui + uic $< >$@ + +$(SDIR)/%_qt.cpp: $(SDIR)/%.h + cd $(SDIR); moc $(notdir $<) -o $(notdir $@) diff --git a/configure b/configure new file mode 100755 index 0000000..15893fe --- /dev/null +++ b/configure @@ -0,0 +1,20 @@ +#!/bin/sh + +PKGS='Qt5Core Qt5Gui Qt5Widgets sfml-network' + +pkg_not_found() { + echo 'pkg-config could not find a required package.' + exit 1 +} + +pkg-config $PKGS || pkg_not_found + +set -f +cxxflags=$(pkg-config --cflags $PKGS) +ldflags=$(pkg-config --libs $PKGS) +set +f + +{ + echo "CXXFLAGS+=$cxxflags" + echo "LDFLAGS+=$ldflags" +} >config.mk