- Server can handle multiple clients simultaneously. - Client can see other players in the game. - Server broadcasts the game state to all clients. - Client receives the game state and renders the other players. - Server assigns a unique ID to each player. - Client receives its player ID form the server. - Server handles client disconnections.. Kinda... Server is ignoring SIGPIPE signal. - Server and client signals are non-blocking. - Moved player objects off the stack and onto the heap.
133 lines
3.8 KiB
C++
133 lines
3.8 KiB
C++
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <vector>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
|
|
#include "bettola/network/socket.h"
|
|
#include "bettola/network/net_common.h"
|
|
#include "bettola/network/message.h"
|
|
#include "bettola/network/player_pos_message.h"
|
|
#include "game/game.h"
|
|
|
|
int main(void) {
|
|
Game game;
|
|
std::vector<BettolaLib::Network::Socket*> client_sockets;
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
printf("=== Bettola Server: Starting ===\n");
|
|
|
|
BettolaLib::Network::Socket server_socket;
|
|
|
|
if(!server_socket.create()) {
|
|
printf("Bettola Server: Failed to create socket.\n");
|
|
return 1;
|
|
}
|
|
|
|
if(!server_socket.bind(BettolaLib::Network::DEFAULT_PORT)) {
|
|
printf("Bettola Server: Failed to bind socket to port %hu.\n",
|
|
BettolaLib::Network::DEFAULT_PORT);
|
|
server_socket.close();
|
|
return 1;
|
|
}
|
|
|
|
if(!server_socket.listen()) {
|
|
printf("Bettola Server: Failed to listen on socket.\n");
|
|
server_socket.close();
|
|
return 1;
|
|
}
|
|
|
|
printf("Bettola Server: Listening on port %hu...\n", BettolaLib::Network::DEFAULT_PORT);
|
|
|
|
/* Set the server socket to non-blocking. */
|
|
int flags = fcntl(server_socket.get_sockfd(), F_GETFL, 0);
|
|
if(flags == -1) {
|
|
perror("fcntl F_GETFL, failed.");
|
|
return 1;
|
|
}
|
|
fcntl(server_socket.get_sockfd(), F_SETFL, flags | O_NONBLOCK);
|
|
|
|
auto last_time = std::chrono::high_resolution_clock::now();
|
|
|
|
/* Main server loop. */
|
|
while(true) {
|
|
/* Accept new connections. */
|
|
BettolaLib::Network::Socket* client_socket = server_socket.accept();
|
|
if(client_socket != nullptr) {
|
|
Player* new_player = game.add_player(client_socket);
|
|
printf("Bettola Server: Client connected! Player ID: %u\n", new_player->get_id());
|
|
|
|
BettolaLib::Network::MessageHeader header;
|
|
header.type = BettolaLib::Network::MessageType::PlayerId;
|
|
header.size = sizeof(unsigned int);
|
|
client_socket->send(&header, sizeof(header));
|
|
unsigned int id = new_player->get_id();
|
|
client_socket->send(&id, sizeof(id));
|
|
|
|
/* Set the client socket to non-blocking. */
|
|
int client_flags = fcntl(client_socket->get_sockfd(), F_GETFL, 0);
|
|
if(client_flags == -1) {
|
|
perror("fcntl F_GETFL failed");
|
|
return 1;
|
|
}
|
|
fcntl(client_socket->get_sockfd(), F_SETFL, client_flags | O_NONBLOCK);
|
|
|
|
client_sockets.push_back(client_socket);
|
|
}
|
|
|
|
/* Process messages from clients. */
|
|
for(auto it = client_sockets.begin(); it != client_sockets.end();) {
|
|
BettolaLib::Network::Socket* client = *it;
|
|
bool client_disconnected = false;
|
|
|
|
while(true) {
|
|
BettolaLib::Network::MessageHeader header;
|
|
/* Non-blocking. */
|
|
ssize_t bytes_received = client->recv(&header, sizeof(header));
|
|
|
|
if(bytes_received == 0) {
|
|
client_disconnected = true;
|
|
break;
|
|
}
|
|
|
|
if(bytes_received < 0) {
|
|
/* No data to read.. */
|
|
break;
|
|
}
|
|
|
|
if(header.type == BettolaLib::Network::MessageType::PlayerPosition) {
|
|
BettolaLib::Network::PlayerPosMessage msg;
|
|
bytes_received = client->recv(&msg, sizeof(msg));
|
|
if(bytes_received > 0) {
|
|
Player* player = game.get_player_by_socket(client);
|
|
if(player) {
|
|
game.update_player_pos(player->get_id(), msg.x, msg.y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(client_disconnected) {
|
|
Player* player = game.get_player_by_socket(client);
|
|
if(player) game.remove_player(player->get_id());
|
|
it = client_sockets.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
/* Broadcase game state. */
|
|
game.broadcast_game_state();
|
|
|
|
/* Sleep for a short time to avoid busy-waiting. */
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000/60));
|
|
}
|
|
|
|
server_socket.close(); /* Shouldn't reach here. */
|
|
printf("=== Bettola Server: Shutting Down ===\n");
|
|
return 0;
|
|
}
|