bettola/srv/main.cpp

179 lines
5.4 KiB
C++

#include <cstddef>
#include <cstdio>
#include <chrono>
#include <sys/select.h>
#include <cstring>
#include <thread>
#include <vector>
#include <fcntl.h>
#include <signal.h>
#include "bettola/network/tcpsocket.h"
#include "bettola/network/udpsocket.h"
#include "bettola/network/net_common.h"
#include "bettola/network/message.h"
#include "bettola/network/player_input_message.h"
#include "game/game.h"
int main(void) {
Game game;
std::vector<BettolaLib::Network::TCPSocket*> client_sockets;
signal(SIGPIPE, SIG_IGN);
printf("=== Bettola Server: Starting ===\n");
BettolaLib::Network::TCPSocket server_socket; /* For TCP connections. */
BettolaLib::Network::UDPSocket udp_socket; /* For UDP traffic. */
/* Create and bind TCP 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: TCP Listening on port %hu...\n", BettolaLib::Network::DEFAULT_PORT);
/* Create and bind UDP socket. */
if(!udp_socket.create()) {
printf("Bettola Server: Failed to create UDP socket.\n");
return 1;
}
if(!udp_socket.bind(BettolaLib::Network::DEFAULT_PORT)) {
printf("Bettola Server: Failed to bind UDP Socket to port %hu.\n",
BettolaLib::Network::DEFAULT_PORT);
udp_socket.close();
return 1;
}
printf("Bettola Server: UDP 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);
int udp_flags = fcntl(udp_socket.get_sockfd(), F_GETFL, 0);
fcntl(udp_socket.get_sockfd(), F_SETFL, udp_flags | O_NONBLOCK);
auto last_time = std::chrono::high_resolution_clock::now();
/* Main server loop. */
while(true) {
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(server_socket.get_sockfd(), &read_fds);
FD_SET(udp_socket.get_sockfd(), &read_fds);
int max_fd = std::max(server_socket.get_sockfd(), udp_socket.get_sockfd());
for(const auto& client : client_sockets) {
FD_SET(client->get_sockfd(), &read_fds);
if(client->get_sockfd() > max_fd) {
max_fd = client->get_sockfd();
}
}
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 1000; /* 1ms */
int activity = select(max_fd+1, &read_fds, nullptr, nullptr, &tv);
if(activity > 0) {
/* Handle new TCP connections. */
if(FD_ISSET(server_socket.get_sockfd(), &read_fds)) {
BettolaLib::Network::TCPSocket* 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));
game.send_initial_chunks(new_player);
int client_flags = fcntl(client_socket->get_sockfd(), F_GETFL, 0);
fcntl(client_socket->get_sockfd(), F_SETFL, client_flags | O_NONBLOCK);
client_sockets.push_back(client_socket);
}
}
/* Handle UDP messages. */
if(FD_ISSET(udp_socket.get_sockfd(), &read_fds)) {
char buffer[1024];
sockaddr_in from_addr;
ssize_t bytes_received;
while((bytes_received = udp_socket.recv_from(buffer, sizeof(buffer), from_addr)) > 0) {
game.process_udp_message(buffer, bytes_received, from_addr);
}
}
/* Handle TCP messages from clients. */
for(auto it = client_sockets.begin(); it != client_sockets.end();) {
BettolaLib::Network::TCPSocket* client = *it;
if(FD_ISSET(client->get_sockfd(), &read_fds)) {
bool client_disconnected = false;
while (true) {
BettolaLib::Network::MessageHeader header;
ssize_t bytes_received = client->recv(&header, sizeof(header));
if(bytes_received == 0) {
client_disconnected = true;
break;
}
if(bytes_received < 0) {
break;
}
}
if(client_disconnected) {
Player* player = game.get_player_by_socket(client);
if(player) game.remove_player(player->get_id());
it = client_sockets.erase(it);
delete client;
} else {
++it;
}
} else {
++it;
}
}
}
/* Broadcase game state. */
game.broadcast_game_state(udp_socket);
/* Sleep for a short time to avoid busy-waiting. */
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
server_socket.close(); /* Shouldn't reach here. */
printf("=== Bettola Server: Shutting Down ===\n");
return 0;
}