diff options
author | Manuel Palenzuela <manuelpalenzuelamerino@gmail.com> | 2020-06-02 20:53:46 +0000 |
---|---|---|
committer | Manuel Palenzuela <manuelpalenzuelamerino@gmail.com> | 2020-06-02 20:53:46 +0000 |
commit | b7b3d05512165e4db9142cdbc64805246909357f (patch) | |
tree | e842ac261b5e08e027c51e6c8e0ec53dac8ef097 | |
download | test-multiplayer-game-b7b3d05512165e4db9142cdbc64805246909357f.tar.gz test-multiplayer-game-b7b3d05512165e4db9142cdbc64805246909357f.tar.bz2 test-multiplayer-game-b7b3d05512165e4db9142cdbc64805246909357f.zip |
Release
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | client.cpp | 201 | ||||
-rw-r--r-- | server.cpp | 197 | ||||
-rw-r--r-- | structs.hpp | 60 | ||||
-rw-r--r-- | utils.hpp | 338 |
6 files changed, 806 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2ad853 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +client +server diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..55000ad --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +CXX = g++ + +CXXFLAGS = -Wall +LIBS = -lncurses + +main: + $(CXX) $(CXXFLAGS) $(LIBS) server.cpp -o server + $(CXX) $(CXXFLAGS) $(LIBS) client.cpp -o client diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000..23fd64d --- /dev/null +++ b/client.cpp @@ -0,0 +1,201 @@ +#include <iostream> +#include <cstring> +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "utils.hpp" + +#define LOCALHOST (char*) "127.0.0.1" +#define PUBLIC (char*) "0.0.0.0" + +#define IP LOCALHOST +#define PORT (char*) "4444" + +int main(int argc, char** argv); +int start_game(Player* player, std::vector<Player*>* players, int* sockfd, int fps); +int connect_to_server(char* ip, char* port, Player* player, int* sockfd); +void* packet_recieveing_thread(void* psockfd); +int move(Player* player, char* key, int* sockfd); + +std::vector<Player*> players; +int max_players = 0; +int fps = 10; +const int server = 0; +int client_id; +bool visual = true; +Player player {}; +std::mutex mutex; + +int main(int argc, char** argv) +{ + int sockfd; + srand(time(0)); + + client_id = rand(); + + player.sender_id = client_id; + player.color = 1; + player.is_color = false; + player.x = rand() % 7; //improve this + player.y = rand() % 7; + + add_player(&player, &players); + + char* ip = IP; + if(argv[1]) + ip = argv[1]; + + char* port = PORT; + if(argv[2]) + port = argv[2]; + + char player_name[20] = "RandomPlayer"; + char num[5]; + sprintf(num, "%d", (int)(time(0) % 9999)); + strncat(player_name, num, sizeof(player_name)); + if(argv[3]) + strncpy(player_name, argv[3], sizeof(player_name)); + strncpy(player.name, player_name, sizeof(player.name)); + + int result = connect_to_server(ip, port, &player, &sockfd); + if(result) + { + std::cout << "[!] Failed to connect to the server! [" << ip << ":" << port << "] (" << result << ")" << std::endl; + return 1; + } + + pthread_t packet_recieveing_thread_id; + pthread_create(&packet_recieveing_thread_id, NULL, packet_recieveing_thread, (void*)(intptr_t)sockfd); + + if(start_game(&player, &players, &sockfd, fps)) + { + std::cout << "[!] Lost connection to server!" << std::endl; + return 2; + } + + if(send_packet(&player, PacketType::packet_disconnect, &sockfd, NULL)) + return 3; + + close(sockfd); + + return 0; +} + +int start_game(Player* player, std::vector<Player*>* players, int* sockfd, int fps) +{ + int status = 0; + setup_ncurses(); + + char key; + while(1) + { + if((rand() % 100) < 10 && ping_server(sockfd)) + { + status = 1; + goto end; + } + + key = getch(); + if(move(player, &key, sockfd)) + { + status = 2; + goto end; + } + + draw_game(players); + usleep(1000000 / fps); + } + + end: + endwin(); + + return status; +} + +int connect_to_server(char* ip, char* port, Player* player, int* sockfd) +{ + *sockfd = socket(AF_INET, SOCK_STREAM, 0); + if(socket < 0) + return 1; + + struct sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = inet_addr(ip); + sockaddr.sin_port = htons(atoi(port)); + + if(connect(*sockfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr))) + return 2; + + std::cout << "[+] Connecting to the game server (" << ip << ":" << port << ")" << std::endl; + + if(send_packet(player, PacketType::packet_connect, sockfd, NULL)) + return 3; + + return 0; +} + +void* packet_recieveing_thread(void* psockfd) +{ + int sockfd = (intptr_t)psockfd; + Packet incoming_packet {}; + + int read_size = 0; + while((read_size = recv(sockfd, &incoming_packet, sizeof(incoming_packet), 0)) > 0) + { + switch(incoming_packet.type) + { + case PacketType::packet_connect: + handle_incoming_packet(&incoming_packet.data.packet_connect.player, &incoming_packet, &players); + send_packet(&player, PacketType::packet_move, &sockfd, NULL); + break; + case PacketType::packet_disconnect: + handle_incoming_packet(&incoming_packet.data.packet_disconnect.player, &incoming_packet, &players); + break; + case PacketType::packet_move: + handle_incoming_packet(&incoming_packet.data.packet_move.player, &incoming_packet, &players); + break; + case PacketType::packet_server_info: + handle_incoming_packet(NULL, &incoming_packet, NULL); + break; + default: + send_packet(&player, PacketType::packet_disconnect, &sockfd, NULL); + exit(1); + break; + } + } + + return 0; +} + +int move(Player* player, char* key, int* sockfd) +{ + //iscolor + switch(*key) + { + case 'q': + return 1; + break; + case 'w': + player->y = player->y - 1; + send_packet(player, PacketType::packet_move, sockfd, NULL); + break; + case 'a': + player->x = player->x - 2; + send_packet(player, PacketType::packet_move, sockfd, NULL); + break; + case 's': + player->y = player->y + 1; + send_packet(player, PacketType::packet_move, sockfd, NULL); + break; + case 'd': + player->x = player->x + 2; + send_packet(player, PacketType::packet_move, sockfd, NULL); + break; + default: + break; + } + + return 0; +} diff --git a/server.cpp b/server.cpp new file mode 100644 index 0000000..71034be --- /dev/null +++ b/server.cpp @@ -0,0 +1,197 @@ +#include <iostream> +#include <cstdlib> +#include <cstring> +#include <algorithm> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> + +#include "utils.hpp" + +#define LOCALHOST (char*) "127.0.0.1" +#define PUBLIC (char*) "0.0.0.0" + +#define IP PUBLIC +#define PORT (char*) "4444" + +int main(int argc, char** argv); +int game_server(int* max_players, std::vector<Player*>* players, const char* ip, const char* port, int* sockfd); +void* server_handler(void* psockaddr); +void* client_handler(void* client_fd); +int waiting_loop(std::vector<Player*>* players, int fps, bool* visual); +int notify_players( std::vector<Player*>* players, Player* sender_player, Packet* packet); +int manual_disconnect(Player* player, std::vector<Player*>* players); + + std::vector<Player*> players; +int max_players; +const int server = 1; +int client_id = 0; +bool visual = false; +int fps = 10; +int sockfd; +std::mutex mutex; + +int main(int argc, char** argv) +{ + max_players = -1; + + char* port = PORT; + + if(argv[1]) + max_players = atoi(argv[1]); + if(argv[2]) + port = argv[2]; + if(argv[3]) + if(!strcmp(argv[3], "-visual")) + visual = true; + + std::cout << "Starting server with access for " << max_players << " players. (" << IP << ":" << port << ")" << std::endl; + + int result = game_server(&max_players, &players, IP, port, &sockfd); + if(result) + std::cout << "[!] Server failed to start! [" << IP << ":" << port << "] (" << result << ")" << std::endl; + else + waiting_loop(&players, fps, &visual); + + return 0; +} + +int game_server(int* max_players, std::vector<Player*>* players, const char* ip, const char* port, int* sockfd) +{ + *sockfd = socket(AF_INET, SOCK_STREAM , 0); + if(*sockfd < 0) + return 1; + + sockaddr_in sockaddr; + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = inet_addr(ip); + sockaddr.sin_port = htons(atoi(port)); + + if(bind(*sockfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr))) + return 2; + + if (listen(*sockfd, 1)) + return 3; + + if(visual) + { + pthread_t server_thread; + if(pthread_create(&server_thread, NULL, server_handler, (void*)&sockaddr)) + return 5; + } + else + server_handler((void*)&sockaddr); + + return 0; +} + +void* server_handler(void* psockaddr) +{ + sockaddr_in* sockaddr = (sockaddr_in*)psockaddr; + socklen_t addrlen = sizeof(*sockaddr); + int connection; + while((connection = accept(sockfd, (struct sockaddr*)sockaddr, &addrlen))) + { + if (connection < 0) + return 0; + + if((int)(&players)->size() == *(&max_players)) + { + std::cout << "[-] Player limit has been reached. Incoming player has been kicked" << std::endl; + fflush(stdout); + close(connection); + continue; + } + + pthread_t sniffer_thread; + if(pthread_create(&sniffer_thread, NULL, client_handler, (void*)(intptr_t)connection)) + return 0; + } + + return 0; +} + +void* client_handler(void* pclient_fd) +{ + struct sockaddr_in client; + socklen_t addrlen = sizeof(client); + getpeername((intptr_t)pclient_fd, (struct sockaddr*)&client, &addrlen); + + Player player {}; + player.fd = (intptr_t)pclient_fd; + strncpy(player.ip, inet_ntoa(client.sin_addr), sizeof(player.ip)); + + Packet incoming_packet {}; + + int read_size = 0; + while((read_size = recv(player.fd, &incoming_packet, sizeof(incoming_packet), 0) > 0)) + { + int result = handle_incoming_packet(&player, &incoming_packet, &players); + if(result > 0) + { + if(strlen(player.name)) + std::cout << "[!] Invalid packet from player! [" << player.name << "] (" << player.ip << ")" << std::endl; + else + std::cout << "[!] Someone tried to connect without using the game's client! (" << player.ip << ")" << std::endl; + + fflush(stdout); + break; + } + + if(result != -1) + notify_players(&players, &player, &incoming_packet); + } + + manual_disconnect(&player, &players); + close((intptr_t)pclient_fd); + + return 0; +} + +int waiting_loop(std::vector<Player*>* players, int fps, bool* visual) +{ + if(*visual) + { + setup_ncurses(); + + while(1) + { + if(getch() == 'q') + goto end; + + draw_game(players); + usleep(1000000 / fps); + } + + end: + endwin(); + } + + return 0; +} + +int notify_players( std::vector<Player*>* players, Player* sender_player, Packet* packet) +{ + for(Player* player : *players) + { + if(player->sender_id != sender_player->sender_id) + if(send(player->fd, (const char*)packet, sizeof(*packet), 0) < 0) + manual_disconnect(player, players); + } + + return 0; +} + +int manual_disconnect(Player* player, std::vector<Player*>* players) +{ + Packet packet {}; + packet.type = PacketType::packet_disconnect; + packet.sender_id = player->sender_id; + packet.data.packet_disconnect.player = *player; + + int result = handle_incoming_packet(player, &packet, players); + notify_players(players, player, &packet); + + return result; +} diff --git a/structs.hpp b/structs.hpp new file mode 100644 index 0000000..94a65b7 --- /dev/null +++ b/structs.hpp @@ -0,0 +1,60 @@ +enum class PacketType +{ + packet_connect, + packet_disconnect, + packet_move, + packet_ping, + packet_server_info +}; + +struct Player +{ + unsigned int sender_id; + int fd; + char name[20]; + char ip[20]; + int x, y; + int color; + bool is_color; +}; + +struct PacketConnect +{ + Player player; +}; + +struct PacketDisconnect +{ + Player player; +}; + +struct PacketMove +{ + Player player; + int x, y; + bool is_color; +}; + +struct PacketPing +{ +}; + +struct PacketServerInfo +{ + int max_players; + int fps; +}; + +struct Packet +{ + PacketType type; + unsigned int sender_id; + union + { + PacketConnect packet_connect; + PacketDisconnect packet_disconnect; + PacketMove packet_move; + PacketPing packet_ping; + PacketServerInfo packet_server_info; + } data; +}; diff --git a/utils.hpp b/utils.hpp new file mode 100644 index 0000000..78fa9d1 --- /dev/null +++ b/utils.hpp @@ -0,0 +1,338 @@ +#include "structs.hpp" + +#include <vector> +#include <mutex> +#include <ncurses.h> + +//#define DEBUG + +int handle_incoming_packet(Player* player, Packet* packet, std::vector<Player*>* players); +int handle_packet_connect(Player* player, PacketConnect* packet, std::vector<Player*>* players); +int handle_packet_disconnect(Player* player, PacketDisconnect* packet, std::vector<Player*>* players); +int handle_packet_move(Player* player, PacketMove* packet, std::vector<Player*>* players); +int handle_packet_server_info(PacketServerInfo* packet); +int add_player(Player* player, std::vector<Player*>* players); +int remove_player(unsigned int sender_id, std::vector<Player*>* players); +int initialize_new_client(Player* player, std::vector<Player*>* players); +int send_packet(Player* player, PacketType packet_type, int* sockfd, void* extra_data); +int ping_server(int* sockfd); +int draw_game(std::vector<Player*>* players); +int setup_ncurses(); +int players_contain( std::vector<Player*>* players, unsigned int sender_id); +void print_debug_players( std::vector<Player*>* players); +void print_player_info(Player* player); + +extern std::vector<Player*> players; +extern int max_players; +extern int fps; +extern const int server; +extern int client_id; +extern bool visual; +extern std::mutex mutex; + +int handle_incoming_packet(Player* player, Packet* packet, std::vector<Player*>* players) +{ + int status; + switch(packet->type) + { + case PacketType::packet_connect: + status = handle_packet_connect(player, &packet->data.packet_connect, players); + break; + case PacketType::packet_disconnect: + status = handle_packet_disconnect(player, &packet->data.packet_disconnect, players); + break; + case PacketType::packet_move: + status = handle_packet_move(player, &packet->data.packet_move, players); + break; + case PacketType::packet_ping: + return -1; + break; + case PacketType::packet_server_info: + status = handle_packet_server_info(&packet->data.packet_server_info); + break; + default: + return 2; + } + + return status; +} + +int handle_packet_connect(Player* player, PacketConnect* packet, std::vector<Player*>* players) +{ + int player_fd; + char player_ip[20]; + + strncpy(player_ip, player->ip, sizeof(player_ip)); + player_fd = player->fd; + memcpy(player, (const char*)&packet->player, sizeof(*player)); + player->fd = player_fd; + strncpy(player->ip, player_ip, sizeof(player->ip)); + memcpy(&packet->player, (const char*)player, sizeof(packet->player)); + + if(add_player(player, players)) + return 1; + + if(server) + { + initialize_new_client(player, players); //send the connect packet of all things to client + + PacketServerInfo server_info {}; + server_info.max_players = max_players; + server_info.fps = fps; + send_packet(player, PacketType::packet_server_info, &player->fd, &server_info); + } + + #ifdef DEBUG + std::cout << "PacketConnect start -----------" << std::endl; + print_debug_players(players); + std::cout << "PacketConnect end -----------" << std::endl; + #endif + return 0; +} + +int handle_packet_disconnect(Player* player, PacketDisconnect* packet, std::vector<Player*>* players) +{ + remove_player(packet->player.sender_id, players); + + #ifdef DEBUG + std::cout << "PacketDisconnect start -----------" << std::endl; + print_debug_players(players); + std::cout << "PacketDisconnect end -----------" << std::endl; + #endif + + return 0; +} + +int handle_packet_move(Player* player, PacketMove* packet, std::vector<Player*>* players) +{ + int index = players_contain(players, player->sender_id); + if(index != -1) + { + //movement checks? + player->x = packet->x; + player->y = packet->y; + + #ifdef DEBUG + std::cout << "PacketMove start -----------" << std::endl; + print_player_info(player); + std::cout << "PacketMove end -----------" << std::endl; + #endif + } + return 0; +} + +int handle_packet_server_info(PacketServerInfo* packet) +{ + max_players = packet->max_players; + fps = packet->fps; + + return 0; +} + +int add_player(Player* player, std::vector<Player*>* players) +{ + if(players_contain(players, player->sender_id) == -1) + { + mutex.lock(); + players->push_back(player); + if(server && !visual) + std::cout << "[+] New Player! [" << player->name << "-" << player->sender_id << "] (" << player->ip << ")\t(" << players->size() << "/" << max_players << " players)" << std::endl; + fflush(stdout); + mutex.unlock(); + } + else + { + if(server && !visual) + std::cout << "[!] A player tried to join with the same player_id as another one! [" << player->name << "-" << player->sender_id << "] (" << player->ip << ")" << std::endl; + } + + + + return 0; +} + +int remove_player(unsigned int sender_id, std::vector<Player*>* players) +{ + int index = players_contain(players, sender_id); + if(index != -1) + { + mutex.lock(); + Player* player = players->at(index); + close(player->fd); + players->erase(players->begin() + index); + if(strlen(player->name)) + if(server && !visual) + std::cout << "[-] Player left! [" << player->name << "-" << player->sender_id << "] (" << player->ip << ")\t(" << players->size() << "/" << max_players << " players)" << std::endl; + fflush(stdout); + mutex.unlock(); + return 0; + } + + return 1; +} + +int initialize_new_client(Player* player, std::vector<Player*>* players) +{ + for(Player* p : *players) + { + if(p->sender_id != player->sender_id) + { + send_packet(p, PacketType::packet_connect, &player->fd, NULL); + std::cout << "initialize x: " << p->x << ", and y: " << p->y << std::endl; + fflush(stdout); + } + } + + return 0; +} + +int send_packet(Player* player, PacketType packet_type, int* sockfd, void* extra_data) +{ + switch(packet_type) + { + case PacketType::packet_connect: + { + Packet packet {}; + packet.type = PacketType::packet_connect; + packet.sender_id = player->sender_id; + packet.data.packet_connect.player = *player; + + if(send(*sockfd, (const char*)&packet, sizeof(packet), 0) < 0) + return 1; + + break; + } + case PacketType::packet_disconnect: + { + Packet packet {}; + packet.sender_id = player->sender_id; + packet.type = PacketType::packet_disconnect; + packet.data.packet_disconnect.player = *player; + + if(send(*sockfd, (const char*)&packet, sizeof(packet), 0) < 0) + return 2; + break; + } + case PacketType::packet_move: + { + Packet packet {}; + packet.sender_id = player->sender_id; + packet.type = PacketType::packet_move; + packet.data.packet_move.x = player->x; + packet.data.packet_move.y = player->y; + packet.data.packet_move.is_color = player->is_color; + packet.data.packet_move.player = *player; + + if(send(*sockfd, (const char*)&packet, sizeof(packet), 0) < 0) + return 3; + break; + } + case PacketType::packet_ping: + { + Packet packet {}; + packet.sender_id = 0; + packet.type = PacketType::packet_ping; + + if(send(*sockfd, (const char*)&packet, sizeof(packet), 0) < 0) + return 4; + + break; + } + case PacketType::packet_server_info: + { + PacketServerInfo* original_packet = (PacketServerInfo*)extra_data; + Packet packet {}; + packet.sender_id = 0; + packet.type = PacketType::packet_server_info; + packet.data.packet_server_info.max_players = original_packet->max_players; + packet.data.packet_server_info.fps = original_packet->fps; + + if(send(*sockfd, (const char*)&packet, sizeof(packet), 0) < 0) + return 5; + + break; + } + default: + return -1; + } + + return 0; +} + +int ping_server(int* sockfd) +{ + return send_packet(NULL, PacketType::packet_ping, sockfd, NULL); +} + +int draw_game(std::vector<Player*>* players) +{ + clear(); + + //iscolor as well + mutex.lock(); + for(Player* p : *players) + { + mvaddstr(p->y, p->x, "x"); + } + mutex.unlock(); + + refresh(); + + return 0; +} + +int setup_ncurses() +{ + initscr(); + clear(); + noecho(); + timeout(0); + curs_set(0); + + return 0; +} + +int players_contain( std::vector<Player*>* players, unsigned int sender_id) +{ + int index = 0; + mutex.lock(); + for(Player* p : *players) + { + if(p->sender_id == sender_id) + { + mutex.unlock(); + return index; + } + index++; + } + + mutex.unlock(); + return -1; +} + +void print_debug_players( std::vector<Player*>* players) +{ + mutex.lock(); + for(Player* player : *players) + { + print_player_info(player); + } + mutex.unlock(); +} + +void print_player_info(Player* player) +{ + std::cout << "--------------------------" << std::endl << std::endl; + + std::cout << "Name: " << player->name << std::endl; + std::cout << "IP: " << player->ip << std::endl; + std::cout << "x: " << player->x << std::endl; + std::cout << "y: " << player->y << std::endl; + std::cout << "color: " << player->color << std::endl; + std::cout << "is_color: " << player->is_color << std::endl; + std::cout << "fd: " << player->fd << std::endl; + std::cout << "id: " << player->sender_id << std::endl; + + std::cout << std::endl << "--------------------------" << std::endl; + fflush(stdout); +} |