#include <SFML/Network.hpp>
#include <cstdlib>
#include <cstdio>
#include <list>
#include <vector>
#include <map>
#include <cstring>
#include <iostream>
#include <sstream>
#include <ios>
#ifdef _WIN32
#include "mingw.thread.h"
#elif __linux__
#include <thread>
#endif
using namespace std;
struct ClientProfile{
enum Status{WAITING_FOR_OPPONENT, JUST_ONLINE, PLAYING, OFFLINE};
typedef unsigned int ID;
ID id;
ClientProfile(){
socket = NULL;
pingCounter = 0;
nPings = 5;
matchID = "NONE";
status = OFFLINE;
}
ClientProfile(sf::TcpSocket* skt, ClientProfile::ID id){
socket = skt;
ip = skt->getRemoteAddress();
nPings = 5;
pingCounter = 0;
matchID = "NONE";
status = JUST_ONLINE;
this->id = id;
}
//PING command stuff
double ping = 0; //milliseconds
sf::Clock pingClock;
int nPings = 5;
int pingCounter = 0;
sf::TcpSocket* socket;
sf::IpAddress ip;
Status status = OFFLINE;
list<sf::Packet> sendQueue;
string matchID;
};
struct Server{
unsigned short port = 8080;
sf::IpAddress publicIP;
sf::IpAddress privateIP;
sf::TcpListener listener;
map<ClientProfile::ID, ClientProfile> clients;
bool online;
list<ClientProfile::ID> clientsOut;
ClientProfile::ID nextClientID = 1;
};
struct Match{
typedef string ID; //each match has its own unique ID
ID id;
string gameMode;
bool pub;
pair<ClientProfile*, ClientProfile*> players;
};
struct MatchServer : public Server{
map<Match::ID, Match> matches; //contains all matches, on-going or not
map<string, list<Match::ID> > waitList; //each game mode has its own wait list
unsigned int nextID = 1;
list<Match::ID> matchesOut;
list<ClientProfile::ID> playersOut;
};
///HELPERS
int str2int(string s);
string int2str(int n, int width = 0);
string db2str(double n);
double str2db(string s);
///SERVER METHODS
void readUserInput(string& userCommand, bool& newCommand);
void showClients(Server& server);
void updateListener(Server& server);
void updateClients(Server& server);
bool startServer(Server& server);
void stopServer(Server& server);
void parseCommandLine(Server& server, int argc, char* argv[]);
void processUserCommand(Server& server, const string command);
void sendPing(ClientProfile& client);
void pingClient(Server& server, ClientProfile::ID clientID);
void pingAll(Server& server);
void showClient(ClientProfile& client);
void processPacket(Server& server, ClientProfile& client, sf::Packet& packet);
ClientProfile* getClientProfile(Server& server, sf::IpAddress ip);
void cleanupServer(Server& server);
void sendPacket(ClientProfile& client, sf::Packet packet);
void sendPacket(ClientProfile& client, sf::String id);
///MATCH SERVER METHODS
void updateMatchConnections(MatchServer& server);
void cleanupMatches(MatchServer& server);
void sendMatchFound(MatchServer& server, ClientProfile& client);
void showAllMatches(MatchServer& server);
void showMatch(Match& match);
int main(int argc, char* argv[]){
MatchServer server;
parseCommandLine(server, argc, argv);
const float targetUPS = 60.f;
const float timeStep = 1.f/targetUPS;
sf::Clock updateClock;
if (!startServer(server)) return -1;
///INPUT THREAD
string userCommand;
bool newCommand = false;
thread userInputThread(readUserInput, ref(userCommand), ref(newCommand));
while(1){
if (newCommand){
string command;
string arg;
stringstream ss(userCommand);
ss >> command;
if (command == "show-online"){
showClients(server);
}else if (command == "stop"){
stopServer(server);
}else if (command == "set-port"){
if (server.online){
printf(" Cannot set port while server is online!\n");
}else{
ss >> server.port;
printf(" Port set to %d\n", server.port);
}
}else if (command == "start" || command == "restart"){
if (server.online){
stopServer(server);
}
startServer(server);
}else if (command == "ping-all"){
pingAll(server);
}else if (command == "ping"){
ss >> arg;
ClientProfile::ID clientID = str2int(arg);
pingClient(server, clientID);
}else if (command == "show-all-matches"){
MatchServer& mserver = static_cast<MatchServer&>(server);
showAllMatches(mserver);
}
newCommand = false;
}
if (updateClock.getElapsedTime().asSeconds() >= timeStep){
if (server.online){
updateListener(server);
updateClients(server);
updateMatchConnections(server);
cleanupServer(server);
cleanupMatches(server);
}
updateClock.restart();
}
}
return 0;
}
void showClients(Server& server){
printf(" Clients: %d\n", server.clients.size());
for (auto& p : server.clients){
showClient(p.second);
}
}
void showAllMatches(MatchServer& server){
printf(" Matches: %d\n", server.matches.size());
for (auto& m : server.matches){
showMatch(m.second);
}
}
void showMatch(Match& match){
ClientProfile::ID p1 = match.players.first ? match.players.first->id : 0;
ClientProfile::ID p2 = match.players.second ? match.players.second->id : 0;
printf("%15s %15d %15d\n", match.id.c_str(), p1, p2);
}
void updateListener(Server& server){
sf::TcpSocket* socket = new sf::TcpSocket;
while (server.listener.accept(*socket) == sf::Socket::Done){
socket->setBlocking(false);
ClientProfile::ID clientID = server.nextClientID++;
ClientProfile client(socket, clientID);
pair<ClientProfile::ID, ClientProfile> p(clientID, client);
server.clients.insert(p);
socket = new sf::TcpSocket;
}
delete socket;
}
void updateClients(Server& server){
for (auto& p : server.clients){
ClientProfile& client = p.second;
sf::Packet packet;
sf::Socket::Status status;
while ((status = client.socket->receive(packet)) == sf::Socket::Done){
processPacket(server, p.second, packet);
}
if (status == sf::Socket::Disconnected){
client.status = ClientProfile::OFFLINE;
server.clientsOut.push_back(client.id);
}
while (!client.sendQueue.empty() && (status = client.socket->send(client.sendQueue.front())) == sf::Socket::Done){
printf("Here\n");
client.sendQueue.pop_front();
}
if (status == sf::Socket::Error){
client.sendQueue.pop_front();
}
}
}
void updateMatchConnections(MatchServer& server){
for (auto& clientID : server.clientsOut){
ClientProfile* client = &server.clients[clientID];
if (client->matchID != "NONE"){
///ANNOUNCE TO ONLINE PLAYERS THAT OPPONENT HAS DISCONNECTED
Match& match = server.matches[client->matchID];
if (match.players.first && match.players.first->status == ClientProfile::PLAYING){
sendPacket(*match.players.first, "PLAYER-DISCONNECTED");
}else if (match.players.second && match.players.second->status == ClientProfile::PLAYING){
sendPacket(*match.players.second, "PLAYER-DISCONNECTED");
}
server.playersOut.push_back(client->id);
}
}
for (auto& clientID : server.playersOut){
ClientProfile* client = &server.clients[clientID];
if (client->matchID != "NONE"){
Match& match = server.matches[client->matchID];
if (match.players.first == client) match.players.first = nullptr;
else if (match.players.second == client) match.players.second = nullptr;
//if (match.players.first == nullptr && match.players.second == nullptr) server.matchesOut.push_back(client->matchID);
///REMOVE MATCH FROM WAIT LIST, IF IN IT
string gameMode = match.gameMode;
server.waitList[gameMode].remove(client->matchID);
///REMOVE MATCH FROM MATCHES
server.matchesOut.push_back(client->matchID);
client->matchID = "NONE";
}
}
server.playersOut.clear();
}
void updateMatchPlayers(MatchServer& server){
}
void cleanupMatches(MatchServer& server){
for (auto& matchID : server.matchesOut){
if (server.matches[matchID].players.first) server.matches[matchID].players.first->matchID = "NONE";
if (server.matches[matchID].players.second) server.matches[matchID].players.second->matchID = "NONE";
server.matches.erase(server.matches.find(matchID));
}
server.matchesOut.clear();
}
void cleanupServer(Server& server){
for (auto& client : server.clientsOut){
map<ClientProfile::ID, ClientProfile>::iterator i = server.clients.find(client);
i->second.socket->disconnect();
delete i->second.socket;
server.clients.erase(i);
}
server.clientsOut.clear();
}
void processPacket(Server& server, ClientProfile& client, sf::Packet& packet){
sf::String id;
sf::Packet oriPacket = packet;
packet >> id;
if (id == "PONG"){
client.pingCounter++;
if (client.pingCounter >= client.nPings){
client.ping /= (double)client.pingCounter;
showClient(client);
}else{
client.ping += client.pingClock.restart().asMilliseconds();
sendPing(client);
}
}else if (id == "FIND-MATCH"){
string gameMode;
packet >> gameMode;
MatchServer& mserver = static_cast<MatchServer&>(server);
if (mserver.waitList.find(gameMode) == mserver.waitList.end()){
mserver.waitList.insert(make_pair(gameMode, list<Match::ID>()));
}
list<Match::ID>& waitList = mserver.waitList[gameMode];
if (waitList.empty()){
Match::ID matchID = int2str(mserver.nextID++);
mserver.matches.insert(make_pair(matchID, Match()));
Match& match = mserver.matches[matchID];
match.id = matchID;
match.players.first = &client;
match.players.second = nullptr;
match.gameMode = gameMode;
waitList.push_back(match.id);
client.matchID = matchID;
}else{
Match::ID matchID = waitList.front();
Match& match = mserver.matches[matchID];
match.players.second = &client;
client.matchID = matchID;
sendPacket(*match.players.first, "MATCH-FOUND");
sendPacket(*match.players.first, "TURN-ENDED");
sendPacket(*match.players.second, "MATCH-FOUND");
waitList.remove(matchID);
}
}else if(id == "LEAVE-MATCH"){
MatchServer& mserver = static_cast<MatchServer&>(server);
Match& match = mserver.matches[client.matchID];
mserver.playersOut.push_back(client.id);
ClientProfile* opponent = (&client == match.players.first) ? match.players.second : match.players.first;
if (opponent != nullptr) sendPacket(*opponent, oriPacket);
}else{
MatchServer& mserver = static_cast<MatchServer&>(server);
Match& match = mserver.matches[client.matchID];
ClientProfile* opponent = (&client == match.players.first) ? match.players.second : match.players.first;
if (opponent != nullptr) sendPacket(*opponent, oriPacket);
}
}
void showClient(ClientProfile& client){
printf(" %15d %15s %15.0f\n", client.id, client.socket->getRemoteAddress().toString().c_str(), client.ping);
}
void pingAll(Server& server){
sf::TcpSocket* c;
for (auto& p : server.clients){
p.second.ping = 0;
p.second.pingCounter = 0;
sendPing(p.second);
}
}
void pingClient(Server& server, ClientProfile::ID clientID){
ClientProfile* client = &server.clients[clientID];
if (client){
client->ping = 0;
client->pingCounter = 0;
sendPing(*client);
}else{
printf(" Client not found: %s\n", client->ip.toString().c_str());
}
}
void sendPing(ClientProfile& client){
client.pingClock.restart();
sendPacket(client, "PING");
}
void sendPacket(ClientProfile& client, sf::Packet pkt){
client.sendQueue.push_back(pkt);
}
void sendPacket(ClientProfile& client, sf::String id){
sf::Packet pkt;
pkt << id;
client.sendQueue.push_back(pkt);
}
ClientProfile* getClientProfile(Server& server, sf::IpAddress ip){
for (auto& p : server.clients){
ClientProfile& client = p.second;
if (client.ip == ip){
return &client;
}
}
return NULL;
}
bool startServer(Server& server){
printf(" Starting server...\n");
server.publicIP = sf::IpAddress::getPublicAddress();
server.privateIP = sf::IpAddress::getLocalAddress();
if (server.publicIP == sf::IpAddress::None){
printf(" Failed to connect to the internet!\n");
server.online = false;
}else{
printf(" Public IP: %s\n", server.publicIP.toString().c_str());
printf(" Private IP: %s\n", server.privateIP.toString().c_str());
printf(" Trying to listen to port %d...\n", server.port);
sf::Socket::Status status = server.listener.listen(server.port);
if (status != sf::Socket::Done){
printf(" Failed to listen to port %d!\n", server.port);
server.online = false;
}else{
server.online = true;
printf(" Successfully binded to port %d\n", server.port);
printf(" Server is now online!\n");
}
}
return server.online;
}
void stopServer(Server& server){
server.online = false;
for (auto p : server.clients){
ClientProfile& client = p.second;
sf::Packet pkt;
client.socket->disconnect();
delete client.socket;
}
server.clients.clear();
server.listener.close();
printf(" Server is now offline!\n");
}
void processUserCommand(Server& server, const string command){
printf("Executing command: %s\n", command.c_str());
}
void parseCommandLine(Server& server, int argc, char* argv[]){
server.listener.setBlocking(false);
for (int i = 0; i < argc; ++i){
if (strcmp(argv[i], "-p") == 0 && i+1 < argc){
server.port = atoi(argv[i+1]);
}
}
}
void readUserInput(string& userCommand, bool& newCommand){
newCommand = false;
int nargs = 3;
int buffSize = 255;
char input[255];
while(1){
if (!newCommand){
cin.getline(input, 255);
newCommand = true;
userCommand = input;
for(int j = 0; j < buffSize; j++) input[j] = '\0';
}
}
}
int str2int(string s){
return atoi(s.c_str());
}
string int2str(int n, int width){
char arg[255];
sprintf(arg, "%%0%dd", width);
char c[255];
sprintf(c, arg, n);
return string(c);
}
string db2str(double n){
char c[255];
sprintf(c, "%.2f", n);
return string(c);
}
double str2db(string s){
return atof(s.c_str());
}