Huge refactor, pulling server and local out into their own binaries

This commit is contained in:
mdiluzio 2015-01-10 16:55:30 +00:00
parent 1b2010faba
commit 0ead12c7dd
16 changed files with 165 additions and 247 deletions

View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 2.8.7)
# Main ttrts library
project( ttrts-system )
# Include the maths
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
../ttrts
)
# Add our sources
set( SOURCES
net.cpp
filesystem.cpp
)
# Add this library
add_library( ${PROJECT_NAME} ${SOURCES} )
target_link_libraries( ${PROJECT_NAME} ttrts pthread )

25
source/system/error.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef _TTRTS_ERROR_H_
#define _TTRTS_ERROR_H_
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
//======================================================================================================================
// Error functions
// For local fatal errors
inline void fatal_error(const char *msg)
{
std::cerr<<msg<<std::endl;
exit(1);
}
// For system fatal errors (ie. functions that set errno)
inline void fatal_perror(const char *msg)
{
perror(msg);
exit(1);
}
#endif

View file

@ -0,0 +1,248 @@
#include "filesystem.h"
#include "net.h"
#include "error.h"
#include <iostream>
#include <fstream>
#include <chrono>
#include <thread>
#include <sys/stat.h>
#include <stdio.h>
#include <stdbool.h>
#include <formatters.h>
#include <unistd.h>
#include <stdlib.h>
// =====================================================================================================================
// time for waiting between file stats
static const std::chrono::milliseconds sk_waitTime = std::chrono::milliseconds(100);
// Check if a file exists
bool FileExists( const std::string& name )
{
struct stat buffer;
return (stat (name.c_str(), &buffer) == 0);
}
// Wait for a file to exist
void WaitForFile( const std::string& name, const std::chrono::milliseconds& time )
{
while( !FileExists(name) ) std::this_thread::sleep_for(time);
}
bool OutputGameStateFile(CTTRTSGame &game)
{
char turnFileName[128];
snprintf(turnFileName,128,"%s%s/Turn_%i.txt", getGamesDir().c_str(),game.GetName().c_str(),game.GetTurn());
std::ofstream turnFile(turnFileName, std::ios_base::trunc); // truncate to overwrite if a file exists
if ( turnFile.bad() )
{
return false;
}
// Output the turn description
std::string turnDescriptor = GetStringFromGame(game);
turnFile<<turnDescriptor;
turnFile.close();
return true;
}
std::string getMapsDir()
{
std::string maps = STRINGIFY(TTRTS_MAPS);
if( getenv("TTRTS_MAPS") )
{
maps = getenv("TTRTS_MAPS");
// Additional trailing slash
if( maps.back() != '/' )
maps += "/";
}
return maps;
}
std::string getGamesDir()
{
std::string dir = STRINGIFY(TTRTS_GAMES);
if( getenv("TTRTS_GAMES") )
{
dir = getenv("TTRTS_GAMES");
// Additional trailing slash
if( dir.back() != '/' )
dir += "/";
}
return dir;
}
CTTRTSGame GetGameFromFile( const std::string& filename )
{
std::string gamefile = filename;
// Default for maps
std::string ttrts_maps_dir = getMapsDir();
// If file path is not local path and file doesn't exist
if( gamefile.find("/") == std::string::npos
&& access( gamefile.c_str(), F_OK ) == -1 )
{
gamefile = ttrts_maps_dir + gamefile;
}
// If still not good
if( access( gamefile.c_str(), F_OK ) == -1 )
fatal_perror("Could not open game file");
std::ifstream file(gamefile);
std::string gameDescriptor;
// Reserve the string needed up front
file.seekg(0, std::ios::end);
gameDescriptor.reserve(file.tellg());
file.seekg(0, std::ios::beg);
// Grab the string from the file
gameDescriptor.assign((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());
if( gameDescriptor.size() == 0 )
fatal_error("failed to read in any information from gamefile");
// Create the game
return GetGameFromString(gameDescriptor);
}
std::string GetOrdersFromPlayerFile(const CTTRTSGame &game, player_t &player)
{
std::string gameDir = getGamesDir();
char playerOrderFileName[128];
snprintf(playerOrderFileName, 128, "%s%s/Player_%i_Turn_%i.txt", gameDir.c_str(),game.GetName().c_str(),(int) player, game.GetTurn());
// Wait for the player order file to be created
std::clog<<"TTRTS: Waiting for "<< playerOrderFileName << std::endl;
bool hasOrderFile = false;
while(!hasOrderFile)
{
WaitForFile(playerOrderFileName,sk_waitTime); // Wait for the file
// File must have END
// Method taken from http://stackoverflow.com/questions/11876290/c-fastest-way-to-read-only-last-line-of-text-file
std::ifstream turnFile(playerOrderFileName);
turnFile.seekg(-1, std::ios_base::end);
// Loop back from the end of file
bool keepLooping = true;
while(keepLooping) {
char ch;
turnFile.get(ch); // Get current byte's data
if((int)turnFile.tellg() <= 1) { // If the data was at or before the 0th byte
turnFile.seekg(0); // The first line is the last line
keepLooping = false; // So stop there
}
else if(ch == '\n') { // If the data was a newline
keepLooping = false; // Stop at the current position.
}
else { // If the data was neither a newline nor at the 0 byte
turnFile.seekg(-2, std::ios_base::cur); // Move to the front of that data, then to the front of the data before it
}
}
// Grab this line
std::string lastLine;
getline(turnFile,lastLine);
if(lastLine == "END")
hasOrderFile = true;
}
std::ifstream turnFile(playerOrderFileName);
// Reserve the full order string
std::string orders;
turnFile.seekg(0, std::ios_base::end);
orders.reserve(turnFile.tellg());
turnFile.seekg(0, std::ios_base::beg);
// Grab the string from the file
orders.assign((std::istreambuf_iterator<char>(turnFile)), std::istreambuf_iterator<char>());
return orders;
}
int CreateAndCleanGameDir(const std::string& gameName)
{
std::string gameDir = getGamesDir()+gameName;
struct stat info;
int ret = stat( gameDir.c_str(), &info );
if( ret == 0 && info.st_mode & S_IFDIR )
{
std::cout<<"TTRTS: " << gameDir << " game directory already exists"<<std::endl;
std::cout<<"TTRTS: Confirm to delete contents [y/N] ";
std::string input;
std::cin>>input;
if( !input.size() || std::tolower(input[0]) != 'y' )
return -1;
}
else if ( ret == 0 )
{
fatal_error("TTRTS_GAMES exists but is not directory \nAborting...");
}
// Create the game directory
char cmd2[128];
snprintf(cmd2,128, "test -d %s || mkdir %s",gameDir.c_str(),gameDir.c_str());
if( system(cmd2) == -1)
fatal_error("Error: Failed to create the game directory");
// Clean out the game directory
char cmd1[128];
snprintf(cmd1,128, "rm -rf %s/*",gameDir.c_str());
if ( system(cmd1) == -1 )
fatal_error("Error: Failed to clean the game directory");
return 0;
}
int OutputGameEnd(const CTTRTSGame &game) {
std::cout<<"TTRTS: Game Over!"<< std::endl;
// Get the winning player
player_t winningPlayer = game.GetWinningPlayer();
// Print the winner!
if ( winningPlayer != player_t::NUM_INVALID )
{
std::cout<<"TTRTS: Winner:"<<(int) winningPlayer <<std::endl;
}
else
{
std::cout<<"TTRTS: It was a draw!"<<std::endl;
}
return (int)winningPlayer;
}
int OutputgameEnd(const CTTRTSGame &game) {
std::cout<<"TTRTS: Game Over!"<< std::endl;
// Get the winning player
player_t winningPlayer = game.GetWinningPlayer();
// Print the winner!
if ( winningPlayer != player_t::NUM_INVALID )
{
std::cout<<"TTRTS: Winner:"<<(int) winningPlayer <<std::endl;
}
else
{
std::cout<<"TTRTS: It was a draw!"<<std::endl;
}
return (int)winningPlayer;
}

View file

@ -0,0 +1,29 @@
#ifndef _TTRTS_FILESYSTEM_H_
#define _TTRTS_FILESYSTEM_H_
#include <string>
#include <chrono>
#include "game.h"
#define STRINGIFY(x) _STRINGIFY(x)
#define _STRINGIFY(x) #x
bool FileExists( const std::string& name );
void WaitForFile( const std::string& name, const std::chrono::milliseconds& time );
bool OutputGameStateFile(CTTRTSGame &game);
std::string GetOrdersFromPlayerFile(const CTTRTSGame &game, player_t &player);
CTTRTSGame GetGameFromFile( const std::string& file );
std::string getMapsDir();
std::string getGamesDir();
int runFromFilesystem(int argc, char* argv[]);
int CreateAndCleanGameDir(const std::string& gameName);
#endif

261
source/system/net.cpp Normal file
View file

@ -0,0 +1,261 @@
#include "net.h"
#include "error.h"
#include <netdb.h>
#include <iostream>
#include <thread>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void WaitForOrdersFromClient(const ClientInfo info, CTTRTSGame &game, std::mutex &mut)
{
char buffer[1028]; // buffer for orders
std::clog<<"TTRTS: Waiting for player "<<(int)info.player<<" at "<<inet_ntoa(info.cli_addr.sin_addr)<<std::endl;
std::string orders;
while ( orders.find("END") == std::string::npos )
{
memset(buffer,0,sizeof(buffer));
// Read in the new socket
// read will block until the client has called write
// up to the full size of the buffer
if (read(info.clientsockfd,buffer,sizeof(buffer)-1) < 0)
fatal_perror("ERROR reading from client");
// Append the received orders
orders+=buffer;
}
std::clog<<"TTRTS: Recieved orders from "<<inet_ntoa(info.cli_addr.sin_addr)<<std::endl;
mut.lock();
game.IssueOrders(info.player , orders);
mut.unlock();
}
void WaitForOrdersFromClients(std::vector<ClientInfo> &myClients, CTTRTSGame &game, std::mutex &gameMutex)
{
// Spawn threads
std::vector<std::thread> clientThreads;
for(auto client : myClients)
{
std::thread clientThread(WaitForOrdersFromClient, client, std::ref(game), ref(gameMutex));
clientThreads.push_back(move(clientThread));
}
// Join up all the threads
for ( std::thread& thread : clientThreads )
{
thread.join();
}
}
void SendGamestateToClients(std::vector<ClientInfo> &myClients, const CTTRTSGame &game, std::mutex &gameMutex)
{
gameMutex.lock();
std::string gamestate_string = GetStringFromGame(game);
gameMutex.unlock();
for (auto client : myClients)
{
// Write to the socket with the buffer
if ( write( client.clientsockfd, gamestate_string.c_str(), gamestate_string.length() ) < 0 )
fatal_perror("ERROR sending to client");
}
}
void PerformClientHandshake(int sockfd, unsigned int &player, std::string &gameNameString)
{
char handshakeBuffer[128];
memset(handshakeBuffer,0,sizeof(handshakeBuffer));
if (read(sockfd, handshakeBuffer,sizeof(handshakeBuffer)-1) < 0)
fatal_perror("ERROR recieving handshake from server");
std::string handshake(handshakeBuffer);
if ( write( sockfd, handshake.c_str(), handshake.length()+1 ) < 0 )
fatal_perror("ERROR sending handshake to server");
char gameName[64];
if ( sscanf(handshake.c_str(),TTRTS_HANDSHAKE_FORMAT,&player,gameName) < 2 )
fatal_error("Handshake failed");
gameNameString = gameName;
}
void PerformServerHandshake(const ClientInfo &client, const std::string &game)
{
char handshake[64];
snprintf(handshake, sizeof(handshake), TTRTS_HANDSHAKE_FORMAT,(unsigned int)client.player,game.c_str());
// Send handshake
if ( write( client.clientsockfd,handshake,sizeof(handshake) ) < 0 )
fatal_perror("ERROR sending to client");
// Receive handshake
char buffer[64];
if ( read(client.clientsockfd,buffer,sizeof(buffer)-1) < 0 )
fatal_perror("ERROR reading from client");
// Verify handshake
if ( std::string(buffer) != std::string(handshake) )
fatal_error("Error in client handshake");
std::clog<<"TTRTS: Success on handshake with player "<<(int)client.player<< std::endl;
}
void TryBindSocket(int sockfd, const sockaddr_in &serv_addr)
{
std::clog<<"TTRTS: Binding to socket"<< std::endl;
int retry = 1;
while (1)
{
if(retry > 10)
fatal_error("Binding failed");
// Attempt to bind our listening socket
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) >= 0)
break;
std::cerr<<"Warning: Binding failed on try "<<retry<< std::endl;
sleep(retry);
retry++;
}
}
ClientInfo &WaitForClientConnection(int sockfd, const std::string &game, ClientInfo &clientInfo)
{
socklen_t clilen = sizeof(sockaddr_in);
// accept waits for a connection from a client
// it returns a new socket file descriptor for this connection
// client information will be stored in cli_addr
clientInfo.clientsockfd = accept(sockfd, (sockaddr *) &clientInfo.cli_addr, &clilen);
if (clientInfo.clientsockfd < 0)
fatal_perror("ERROR on accept");
std::clog<<"TTRTS: Client connected from "<<inet_ntoa(clientInfo.cli_addr.sin_addr)<<" socket "<<clientInfo.clientsockfd<< std::endl;
// Handshake with client
PerformServerHandshake(clientInfo, game);
return clientInfo;
}
int SetUpServerListeningSocket(const sockaddr_in &serv_addr)
{
int sockfd;
std::clog<<"TTRTS: Opening socket"<< std::endl;
// Create a new socket
// AF_INET is general internet socket domain
// SOCK_STREAM as messages will be read in on this socket, SOCK_DGRAM would be for packets
// 0 is for default protocol
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
fatal_perror("ERROR opening socket");
// bind our socket to this server address
TryBindSocket(sockfd, serv_addr);
// Listen on the socket for messages
// Second param is length of backlog queue, the maximum number of connections
// that can be waiting while the process is handling a single connection
// max is usually set to 5
listen(sockfd,5);
return sockfd;
}
sockaddr_in GetLocalServerAddress()
{
sockaddr_in serv_addr; // Server address
// empty the server address
memset(&serv_addr,0, sizeof(serv_addr));
// Set the server address family to AF_INET
serv_addr.sin_family = AF_INET;
// htons swaps from host byte order to network byte order
serv_addr.sin_port = htons(TTRTS_PORT);
// The host for this address is this current machine's IP, INADDR_ANY fetches this
serv_addr.sin_addr.s_addr = INADDR_ANY;
return serv_addr;
}
int ConnectToHostServer(const std::string &hostname, sockaddr_in &serv_addr)
{
// Create a new socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
fatal_perror("ERROR opening socket");
// Get the hostent information for the host by name
hostent *server = gethostbyname(hostname.c_str());
if (server == NULL)
fatal_error("ERROR, no such host");
// copy the server address into our server_addr struct
memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
// Attempt to connect to the server using the socket and server address info
if (connect(sockfd, (const sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
fatal_perror("ERROR connecting");
return sockfd;
}
std::string WaitForGamestateMessage(int sockfd)
{
std::string gamestate;
char gamestateBuffer[1028];
while( gamestate.find("END") == std::string::npos )
{
memset(gamestateBuffer,0,sizeof(gamestateBuffer));
// Receive gamestate
if (read(sockfd,gamestateBuffer,sizeof(gamestateBuffer)-1) < 0)
fatal_perror("ERROR reading from client");
gamestate+=gamestateBuffer;
}
return gamestate;
}
int SendOrdersToServer(int sockfd, const std::string &orders)
{
int n = write(sockfd,orders.c_str(),orders.length());
if (n < 0)
fatal_perror("ERROR writing to socket");
return n;
}
int OutputGameEnd( CTTRTSGame& game )
{
std::cout<<"TTRTS: Game Over!"<<std::endl;
// Get the winning player
player_t winningPlayer = game.GetWinningPlayer();
// Print the winner!
if ( winningPlayer != player_t::NUM_INVALID )
{
std::cout<<"TTRTS: Winner:"<<(int) winningPlayer <<std::endl;
}
else
{
std::cout<<"TTRTS: It was a draw!"<<std::endl;
}
return (int)winningPlayer;
}

81
source/system/net.h Normal file
View file

@ -0,0 +1,81 @@
#ifndef _TTRTS_NET_H_
#define _TTRTS_NET_H_
#include <vector>
#include <mutex>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <game.h>
#include <formatters.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#define TTRTS_HANDSHAKE_FORMAT "player %u name %s"
//======================================================================================================================
// Structs for net management
// Struct for net client info
struct ClientInfo
{
sockaddr_in cli_addr;
int clientsockfd;
player_t player;
};
//======================================================================================================================
// Server side function
// Get the address of a local server
sockaddr_in GetLocalServerAddress();
// Set up a new listening socket for the server
int SetUpServerListeningSocket(const sockaddr_in &serv_addr);
// Wait for client connection on listening socket sockfd
// Will fill clientInfo with client information
ClientInfo &WaitForClientConnection(int sockfd, const std::string &game, ClientInfo &clientInfo);
// Wait for orders from a client, will not return until client has send valid orders
// Will automatically add orders to the game
void WaitForOrdersFromClient(const ClientInfo info, CTTRTSGame &game, std::mutex &mut);
// Iterates through a list of clients calling WaitForOrdersFromClient
void WaitForOrdersFromClients(std::vector<ClientInfo> &myClients, CTTRTSGame &game, std::mutex &gameMutex);
// Sends current gamestate to each client
void SendGamestateToClients(std::vector<ClientInfo> &myClients, const CTTRTSGame &game, std::mutex &gameMutex);
// Tries to bind to a socket, will attempt 10 times with longer waits between
void TryBindSocket(int sockfd, const sockaddr_in &serv_addr);
// Perform the server side handshake with a client
void PerformServerHandshake(const ClientInfo &client, const std::string &game);
//======================================================================================================================
// Client side functions
// Connect to the host, returns new socket for communication
int ConnectToHostServer(const std::string &hostname, sockaddr_in &serv_addr);
// Perform the client side handshake with the server
void PerformClientHandshake(int sockfd, unsigned int &player, std::string &gameNameString);
// Wait for gamestate message from host
std::string WaitForGamestateMessage(int sockfd);
// Send orders to the server
int SendOrdersToServer(int sockfd, const std::string &orders);
//======================================================================================================================
// Other functions
int OutputGameEnd( CTTRTSGame& game );
#endif