From a2a8b052aff38db5e7e656f02ab671ab2bc1995d Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Sun, 14 Sep 2025 00:52:18 +0100 Subject: [PATCH] feat(network): hybrid TCP/UDP networking model. Moving from a TCP only model to a hybrid TCP/UDP system. This is required to achieve a responsive, real-time multiplayer experience. --- .../bettola/network/player_pos_message.h | 1 + .../bettola/network/{socket.h => tcpsocket.h} | 8 +- .../include/bettola/network/udpsocket.h | 26 +++ .../network/{socket.cpp => tcpsocket.cpp} | 25 ++- libbettola/src/bettola/network/udpsocket.cpp | 62 +++++++ src/bettola.cpp | 157 ++++++++++++------ src/bettola.h | 12 +- srv/game/game.cpp | 49 ++++-- srv/game/game.h | 17 +- srv/game/player.cpp | 10 +- srv/game/player.h | 16 +- srv/main.cpp | 70 +++++--- 12 files changed, 339 insertions(+), 114 deletions(-) rename libbettola/include/bettola/network/{socket.h => tcpsocket.h} (82%) create mode 100644 libbettola/include/bettola/network/udpsocket.h rename libbettola/src/bettola/network/{socket.cpp => tcpsocket.cpp} (83%) create mode 100644 libbettola/src/bettola/network/udpsocket.cpp diff --git a/libbettola/include/bettola/network/player_pos_message.h b/libbettola/include/bettola/network/player_pos_message.h index a769d57..77cd9ae 100644 --- a/libbettola/include/bettola/network/player_pos_message.h +++ b/libbettola/include/bettola/network/player_pos_message.h @@ -6,6 +6,7 @@ namespace BettolaLib { namespace Network { struct PlayerPosMessage { + unsigned int player_id; float x; float y; }; diff --git a/libbettola/include/bettola/network/socket.h b/libbettola/include/bettola/network/tcpsocket.h similarity index 82% rename from libbettola/include/bettola/network/socket.h rename to libbettola/include/bettola/network/tcpsocket.h index 5e11439..053c8e5 100644 --- a/libbettola/include/bettola/network/socket.h +++ b/libbettola/include/bettola/network/tcpsocket.h @@ -9,15 +9,15 @@ namespace BettolaLib { namespace Network { -class Socket { +class TCPSocket { public: - Socket(void); - ~Socket(void); + TCPSocket(void); + ~TCPSocket(void); bool create(void); bool bind(unsigned short port); bool listen(int backlog = 5); - Socket* accept(void); /* Return a new Socket for the accepted connection. */ + TCPSocket* accept(void); /* Return a new Socket for the accepted connection. */ bool connect(const std::string& ip_address, unsigned short port); ssize_t send(const void* buffer, size_t length); diff --git a/libbettola/include/bettola/network/udpsocket.h b/libbettola/include/bettola/network/udpsocket.h new file mode 100644 index 0000000..198cc48 --- /dev/null +++ b/libbettola/include/bettola/network/udpsocket.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +namespace BettolaLib { +namespace Network { +class UDPSocket { +public: + UDPSocket(void); + ~UDPSocket(void); + + bool create(void); + bool bind(unsigned short port); + ssize_t send_to(const void* data, size_t size, const sockaddr_in& dest_addr); + ssize_t recv_from(void* data, size_t size, sockaddr_in& sender_addr); + void close(void); + + int get_sockfd(void) const { return _sockfd; } +private: + int _sockfd; +}; + +} /* namespace Network. */ +} /* namespace BettolaLib. */ diff --git a/libbettola/src/bettola/network/socket.cpp b/libbettola/src/bettola/network/tcpsocket.cpp similarity index 83% rename from libbettola/src/bettola/network/socket.cpp rename to libbettola/src/bettola/network/tcpsocket.cpp index 8f6976d..beb93e7 100644 --- a/libbettola/src/bettola/network/socket.cpp +++ b/libbettola/src/bettola/network/tcpsocket.cpp @@ -7,21 +7,20 @@ #include #include -#include "bettola/network/socket.h" -#include "bettola/network/net_common.h" +#include "bettola/network/tcpsocket.h" namespace BettolaLib { namespace Network { -Socket::Socket(void) : _sockfd(-1) { +TCPSocket::TCPSocket(void) : _sockfd(-1) { memset(&_address, 0, sizeof(_address)); } -Socket::~Socket(void) { +TCPSocket::~TCPSocket(void) { this->close(); } -bool Socket::create(void) { +bool TCPSocket::create(void) { _sockfd = socket(AF_INET, SOCK_STREAM, 0); if(_sockfd == -1) { fprintf(stderr, "Socket::create() failed with errno %d: %s\n", errno, strerror(errno)); @@ -39,7 +38,7 @@ bool Socket::create(void) { } -bool Socket::bind(unsigned short port) { +bool TCPSocket::bind(unsigned short port) { _address.sin_family = AF_INET; _address.sin_addr.s_addr = INADDR_ANY; _address.sin_port = htons(port); @@ -57,7 +56,7 @@ bool Socket::bind(unsigned short port) { return true; } -bool Socket::listen(int backlog) { +bool TCPSocket::listen(int backlog) { if(::listen(_sockfd, backlog) == -1) { perror("Socket accept failed."); return false; @@ -65,7 +64,7 @@ bool Socket::listen(int backlog) { return true; } -Socket* Socket::accept(void) { +TCPSocket* TCPSocket::accept(void) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_sockfd = ::accept(_sockfd, (struct sockaddr*)&client_addr, &client_len); @@ -75,13 +74,13 @@ Socket* Socket::accept(void) { return nullptr; } - Socket* new_socket = new Socket(); + TCPSocket* new_socket = new TCPSocket(); new_socket->_sockfd = client_sockfd; new_socket->_address = client_addr; return new_socket; } -bool Socket::connect(const std::string& ip_address, unsigned short port) { +bool TCPSocket::connect(const std::string& ip_address, unsigned short port) { _address.sin_family = AF_INET; _address.sin_port = htons(port); @@ -104,7 +103,7 @@ bool Socket::connect(const std::string& ip_address, unsigned short port) { return true; } -ssize_t Socket::send(const void* buffer, size_t length) { +ssize_t TCPSocket::send(const void* buffer, size_t length) { ssize_t bytes_sent = ::send(_sockfd, buffer, length, 0); if(bytes_sent == -1) { perror("Send failed."); @@ -112,7 +111,7 @@ ssize_t Socket::send(const void* buffer, size_t length) { return bytes_sent; } -ssize_t Socket::recv(void* buffer, size_t length) { +ssize_t TCPSocket::recv(void* buffer, size_t length) { ssize_t bytes_received = ::recv(_sockfd, buffer, length, 0); if(bytes_received == -1 && errno != EWOULDBLOCK && errno != EAGAIN) { perror("Receive failed."); @@ -120,7 +119,7 @@ ssize_t Socket::recv(void* buffer, size_t length) { return bytes_received; } -void Socket::close(void) { +void TCPSocket::close(void) { if(_sockfd != -1) { ::close(_sockfd); _sockfd = -1; diff --git a/libbettola/src/bettola/network/udpsocket.cpp b/libbettola/src/bettola/network/udpsocket.cpp new file mode 100644 index 0000000..301b6e1 --- /dev/null +++ b/libbettola/src/bettola/network/udpsocket.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + +#include "bettola/network/udpsocket.h" + +using namespace BettolaLib::Network; + +UDPSocket::UDPSocket(void) : _sockfd(-1) {} + +UDPSocket::~UDPSocket(void) { + close(); +} + +bool UDPSocket::create(void) { + _sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if(_sockfd < 0) { + perror("socket (udp)"); + return false; + } + return true; +} + +bool UDPSocket::bind(unsigned short port) { + sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(port); + + if(::bind(_sockfd, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + perror("bind (udp)"); + return false; + } + return true; +} + +ssize_t UDPSocket::send_to(const void* data, size_t size, const sockaddr_in& dest_addr) { + ssize_t bytes_sent = ::sendto(_sockfd, data, size, 0, (sockaddr*)&dest_addr, sizeof(dest_addr)); + if(bytes_sent < 0) { + perror("sento"); + } + return bytes_sent; +} + +ssize_t UDPSocket::recv_from(void* data, size_t size, sockaddr_in& sender_addr) { + socklen_t sender_len = sizeof(sender_addr); + ssize_t bytes_receieved = ::recvfrom(_sockfd, data, size, 0, (sockaddr*)&sender_addr, &sender_len); + if(bytes_receieved < 0 && errno != EWOULDBLOCK) { + perror("recvfrom"); + } + return bytes_receieved; +} + +void UDPSocket::close(void) { + if(_sockfd >= 0) { + ::close(_sockfd); + _sockfd = -1; + } +} diff --git a/src/bettola.cpp b/src/bettola.cpp index 7a471d5..be1dfe8 100644 --- a/src/bettola.cpp +++ b/src/bettola.cpp @@ -6,13 +6,13 @@ #include #include #include +#include #include -#include +#include #include #include #include "bettola.h" -#include "bettola/network/socket.h" #include "bettola/network/net_common.h" #include "math/mat4.h" #include "network/message.h" @@ -29,14 +29,19 @@ Bettola::Bettola(void) : _gl_context(nullptr), _vao(0), _vbo(0), - _our_player_id(0) {} + _our_player_id(0) { + + memset(&_server_addr, 0, sizeof(_server_addr)); +} Bettola::~Bettola(void) { if(_gl_context) { SDL_GL_DestroyContext(_gl_context); } - _client_socket.close(); /* Explicity close client socket. */ + /* Explicitly close client socket. */ + _tcp_socket.close(); + _udp_socket.close(); if(_window) { SDL_DestroyWindow(_window); @@ -85,47 +90,8 @@ int Bettola::run(void) { delta_time = (double)(current_counter-last_count) / (double)perf_freq; last_count = current_counter; - /* Pretty simple network ineteraction currently. */ - static double network_time = 0.0; - network_time += delta_time; - if(network_time > 2.0) { - /* Send/receive every 2 seconds. */ - BettolaLib::Network::MessageHeader header; - header.type = BettolaLib::Network::MessageType::PlayerPosition; - header.size = sizeof(BettolaLib::Network::PlayerPosMessage); - _client_socket.send(&header, sizeof(header)); - - BettolaLib::Network::PlayerPosMessage msg; - msg.x = _player.get_x(); - msg.y = _player.get_y(); - _client_socket.send(&msg, sizeof(msg)); - - network_time = 0.0; - } - - if(_our_player_id == 0) { - BettolaLib::Network::MessageHeader id_header; - ssize_t id_bytes_received = _client_socket.recv(&id_header, sizeof(id_header)); - if(id_bytes_received > 0 && id_header.type == BettolaLib::Network::MessageType::PlayerId) { - _client_socket.recv(&_our_player_id, sizeof(_our_player_id)); - } - } - - while(true) { - BettolaLib::Network::MessageHeader header; - ssize_t bytes_received = _client_socket.recv(&header, sizeof(header)); - if(bytes_received <= 0) { - break; - } - if(header.type == BettolaLib::Network::MessageType::GameState) { - BettolaLib::Network::GameStateMessage msg; - if(_client_socket.recv(&msg, sizeof(msg)) > 0) { - update_remote_players(msg); - } - } - } - process_events(); + process_network(); update(delta_time); render(); } @@ -156,6 +122,70 @@ void Bettola::process_events(void) { } } +void Bettola::process_network(void) { + fd_set read_fds; + FD_ZERO(&read_fds); + + int tcp_fd = _tcp_socket.get_sockfd(); + int udp_fd = _udp_socket.get_sockfd(); + + FD_SET(tcp_fd, &read_fds); + FD_SET(udp_fd, &read_fds); + + int max_fd = std::max(tcp_fd, udp_fd); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; /* Non-blocking. */ + + int activity = select(max_fd+1, &read_fds, nullptr, nullptr, &tv); + + if(activity > 0) { + if(FD_ISSET(tcp_fd, &read_fds)) { + /* We only expect player ID assignment for now. */ + if(_our_player_id == 0) { + BettolaLib::Network::MessageHeader id_header; + ssize_t id_bytes_received = _tcp_socket.recv(&id_header, sizeof(id_header)); + if(id_bytes_received > 0 && id_header.type == BettolaLib::Network::MessageType::PlayerId) { + _tcp_socket.recv(&_our_player_id, sizeof(_our_player_id)); + printf("Bettola: Assigned player ID: %u\n", _our_player_id); + } + } + } + + if(FD_ISSET(udp_fd, &read_fds)) { + process_udp_messages(); + } + } +} + +void Bettola::process_udp_messages(void) { + char buffer[1024]; + sockaddr_in from_addr; + ssize_t id_bytes_received; + + while((id_bytes_received = _udp_socket.recv_from(buffer,sizeof(buffer),from_addr))>0) { + if(id_bytes_received < sizeof(BettolaLib::Network::MessageHeader)) { + continue; + } + + BettolaLib::Network::MessageHeader header; + memcpy(&header, buffer, sizeof(header)); + memcpy(&header, buffer, sizeof(header)); + + const char* payload = buffer + sizeof(BettolaLib::Network::MessageHeader); + size_t payload_size = id_bytes_received-sizeof(BettolaLib::Network::MessageHeader); + + if(header.type == BettolaLib::Network::MessageType::GameState) { + if(payload_size >= sizeof(BettolaLib::Network::GameStateMessage)) { + BettolaLib::Network::GameStateMessage msg; + memcpy(&msg, payload, sizeof(msg)); + update_remote_players(msg); + } + } + } +} + void Bettola::update(double dt) { float dir_x = 0.0f; float dir_y = 0.0f; @@ -167,6 +197,25 @@ void Bettola::update(double dt) { _player.update(dt); + if(_our_player_id > 0) { + /* Send player position to server via UDP.. */ + BettolaLib::Network::MessageHeader header; + header.type = BettolaLib::Network::MessageType::PlayerPosition; + header.size = sizeof(BettolaLib::Network::PlayerPosMessage); + + BettolaLib::Network::PlayerPosMessage msg; + msg.player_id = _our_player_id; + msg.x = _our_player_id; + msg.x = _player.get_x(); + msg.y = _player.get_y(); + + char buffer[sizeof(header) + sizeof(msg)]; + memcpy(buffer, &header, sizeof(header)); + memcpy(buffer + sizeof(header), &msg, sizeof(msg)); + + _udp_socket.send_to(buffer, sizeof(buffer), _server_addr); + } + static char window_title[256]; static double time_since_title_update = 0.0; @@ -315,24 +364,34 @@ void Bettola::setup_quad(void) { bool Bettola::init_client_connection(void) { SDL_Delay(1000); /* Wait a second before trying to connect. */ - if(!_client_socket.create()) { + if(!_tcp_socket.create()) { printf("Bettola client: Failed to create socket.\n"); return false; } - if(!_client_socket.connect("127.0.0.1", BettolaLib::Network::DEFAULT_PORT)) { + if(!_tcp_socket.connect("127.0.0.1", BettolaLib::Network::DEFAULT_PORT)) { perror("Bettola Client: Failed to connect to server.\n"); - //_client_socket.close(); return false; } + if(!_udp_socket.create()) { + printf("Bettola Client: Failed to create UDP socket.\n"); + return false; + } + + /* Setup server address for UDP. */ + _server_addr.sin_family = AF_INET; + _server_addr.sin_port = htons(BettolaLib::Network::DEFAULT_PORT); + inet_pton(AF_INET, "127.0.0.1", &_server_addr.sin_addr); + /* Set the socket to non-blocking. */ - int flags = fcntl(_client_socket.get_sockfd(), F_GETFL, 0); + int flags = fcntl(_tcp_socket.get_sockfd(), F_GETFL, 0); if(flags == -1) { perror("fcntl F_GETFL failed."); return false; } - fcntl(_client_socket.get_sockfd(), F_SETFL, flags | O_NONBLOCK); + fcntl(_tcp_socket.get_sockfd(), F_SETFL, flags | O_NONBLOCK); + fcntl(_udp_socket.get_sockfd(), F_SETFL, flags | O_NONBLOCK); printf("Bettola Client: Connected to server at 127.0.01.:%hu\n", BettolaLib::Network::DEFAULT_PORT); return true; diff --git a/src/bettola.h b/src/bettola.h index 17817ea..fc0f9c3 100644 --- a/src/bettola.h +++ b/src/bettola.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -7,8 +8,9 @@ #include "game/player.h" #include "game/remote_player.h" #include "math/mat4.h" -#include "bettola/network/socket.h" -#include "network/game_state_message.h" +#include "network/tcpsocket.h" +#include "network/udpsocket.h" +namespace BettolaLib { namespace Network { struct GameStateMessage; } } class Bettola { public: @@ -19,6 +21,8 @@ public: private: void process_events(void); + void process_network(void); + void process_udp_messages(void); void update(double dt); void render(void); void update_remote_players(const BettolaLib::Network::GameStateMessage& msg); @@ -53,5 +57,7 @@ private: Player _player; InputState _input; std::vector _remote_players; - BettolaLib::Network::Socket _client_socket; + BettolaLib::Network::TCPSocket _tcp_socket; + BettolaLib::Network::UDPSocket _udp_socket; + sockaddr_in _server_addr; }; diff --git a/srv/game/game.cpp b/srv/game/game.cpp index a2f2d9e..ff693ae 100644 --- a/srv/game/game.cpp +++ b/srv/game/game.cpp @@ -1,12 +1,15 @@ #include +#include #include #include "game.h" #include "network/game_state_message.h" -#include "network/socket.h" +#include "network/player_pos_message.h" +#include "network/tcpsocket.h" #include "network/message.h" +#include "network/udpsocket.h" -Player* Game::add_player(BettolaLib::Network::Socket* socket) { +Player* Game::add_player(BettolaLib::Network::TCPSocket* socket) { _players.push_back(new Player(socket)); return _players.back(); } @@ -24,17 +27,25 @@ void Game::remove_player(unsigned int player_id) { _players.end()); } -void Game::update_player_pos(unsigned int player_id, float x, float y) { - auto it = std::find_if(_players.begin(), _players.end(), - [player_id](const Player* player) { - return player->get_id() == player_id; - }); - if(it != _players.end()) { - (*it)->set_position(x, y); +void Game::handle_udp_message(const BettolaLib::Network::MessageHeader& header, + const char* buffer, const sockaddr_in& from_addr) { + + if(header.type == BettolaLib::Network::MessageType::PlayerPosition) { + BettolaLib::Network::PlayerPosMessage msg; + memcpy(&msg, buffer, sizeof(msg)); + + Player* player = get_player_by_id(msg.player_id); + if(player) { + if(!player->has_udp_addr()) { + printf("Bettola Server: Associated UDP address for player %u\n", msg.player_id); + player->set_udp_addr(from_addr); + } + player->set_position(msg.x, msg.y); + } } } -void Game::broadcast_game_state(void) { +void Game::broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket) { BettolaLib::Network::GameStateMessage msg; msg.num_players = _players.size(); memset(msg.players, 0, sizeof(msg.players)); @@ -50,13 +61,14 @@ void Game::broadcast_game_state(void) { header.size = sizeof(msg); for(const auto& player : _players) { - BettolaLib::Network::Socket& socket = player->get_socket(); - socket.send(&header, sizeof(header)); - socket.send(&msg, sizeof(msg)); + if(player->has_udp_addr()) { + udp_socket.send_to(&header, sizeof(header), player->get_udp_addr()); + udp_socket.send_to(&msg, sizeof(msg), player->get_udp_addr()); + } } } -Player* Game::get_player_by_socket(BettolaLib::Network::Socket* socket) { +Player* Game::get_player_by_socket(const BettolaLib::Network::TCPSocket* socket) { auto it = std::find_if(_players.begin(), _players.end(), [socket](const Player* player) { return &player->get_socket() == socket; @@ -65,3 +77,12 @@ Player* Game::get_player_by_socket(BettolaLib::Network::Socket* socket) { return(it != _players.end() ? *it : nullptr); } +Player* Game::get_player_by_id(unsigned int id) { + auto it = std::find_if(_players.begin(), _players.end(), + [id](const Player* player) { + return player->get_id() == id; + }); + + return(it != _players.end() ? *it : nullptr); +} + diff --git a/srv/game/game.h b/srv/game/game.h index d2464bb..99aff3d 100644 --- a/srv/game/game.h +++ b/srv/game/game.h @@ -1,16 +1,23 @@ #pragma once +#include #include -#include "bettola/network/socket.h" +#include "bettola/network/tcpsocket.h" +#include "bettola/network/udpsocket.h" +#include "network/message.h" #include "player.h" class Game { public: - Player* add_player(BettolaLib::Network::Socket* socket); + Player* add_player(BettolaLib::Network::TCPSocket* socket); void remove_player(unsigned int player_id); - void update_player_pos(unsigned int player_id, float x, float y); - void broadcast_game_state(void); - Player* get_player_by_socket(BettolaLib::Network::Socket* socket); + + void handle_udp_message(const BettolaLib::Network::MessageHeader& header, + const char* buffer, const sockaddr_in& from_addr); + void broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket); + + Player* get_player_by_socket(const BettolaLib::Network::TCPSocket* socket); + Player* get_player_by_id(unsigned int id); private: std::vector _players; diff --git a/srv/game/player.cpp b/srv/game/player.cpp index ca460c7..159e53f 100644 --- a/srv/game/player.cpp +++ b/srv/game/player.cpp @@ -1,8 +1,12 @@ +#include + #include "player.h" -#include "network/socket.h" +#include "network/tcpsocket.h" unsigned int Player::_next_player_id = 0; -Player::Player(BettolaLib::Network::Socket* socket) : - _id(_next_player_id++), _x(0.0f), _y(0.0f), _socket(socket) {} +Player::Player(BettolaLib::Network::TCPSocket* socket) : + _id(_next_player_id++), _x(0.0f), _y(0.0f), _socket(socket), _has_udp_addr(false) { + memset(&_udp_addr, 0, sizeof(_udp_addr)); +} diff --git a/srv/game/player.h b/srv/game/player.h index c9190de..c17833c 100644 --- a/srv/game/player.h +++ b/srv/game/player.h @@ -1,17 +1,23 @@ #pragma once -#include "bettola/network/socket.h" +#include + +#include "bettola/network/tcpsocket.h" class Player { public: - Player(BettolaLib::Network::Socket* socket); + Player(BettolaLib::Network::TCPSocket* socket); void set_position(float x, float y) { _x = x; _y = y; } + void set_udp_addr(const sockaddr_in& addr) { _udp_addr = addr; _has_udp_addr = true; } unsigned int get_id(void) const { return _id; } float get_x(void) const { return _x; } float get_y(void) const { return _y; } - BettolaLib::Network::Socket& get_socket(void) const { return *_socket; } + + BettolaLib::Network::TCPSocket& get_socket(void) const { return *_socket; } + const sockaddr_in& get_udp_addr(void) const { return _udp_addr; } + bool has_udp_addr(void) const { return _has_udp_addr; } private: static unsigned int _next_player_id; @@ -19,5 +25,7 @@ private: unsigned int _id; float _x; float _y; - BettolaLib::Network::Socket* _socket; + BettolaLib::Network::TCPSocket* _socket; + sockaddr_in _udp_addr; + bool _has_udp_addr; }; diff --git a/srv/main.cpp b/srv/main.cpp index c629356..4daf2e7 100644 --- a/srv/main.cpp +++ b/srv/main.cpp @@ -2,12 +2,14 @@ #include #include #include +#include #include #include #include #include -#include "bettola/network/socket.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_pos_message.h" @@ -15,14 +17,16 @@ int main(void) { Game game; - std::vector client_sockets; + std::vector client_sockets; signal(SIGPIPE, SIG_IGN); printf("=== Bettola Server: Starting ===\n"); - BettolaLib::Network::Socket server_socket; + 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; @@ -41,7 +45,22 @@ int main(void) { return 1; } - printf("Bettola Server: Listening on port %hu...\n", BettolaLib::Network::DEFAULT_PORT); + 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); @@ -51,6 +70,9 @@ int main(void) { } 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. */ @@ -58,7 +80,9 @@ int main(void) { fd_set read_fds; FD_ZERO(&read_fds); FD_SET(server_socket.get_sockfd(), &read_fds); - int max_fd = server_socket.get_sockfd(); + 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); @@ -74,8 +98,9 @@ int main(void) { 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::Socket* client_socket = server_socket.accept(); + 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()); @@ -94,8 +119,26 @@ int main(void) { } } + /* 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) { + if(bytes_received >= sizeof(BettolaLib::Network::MessageHeader)) { + BettolaLib::Network::MessageHeader header; + memcpy(&header, buffer, sizeof(header)); + if(bytes_received >= sizeof(BettolaLib::Network::MessageHeader) + header.size) { + game.handle_udp_message(header, buffer+sizeof(BettolaLib::Network::MessageHeader), from_addr); + } + } + } + } + + /* Handle TCP messages from clients. */ for(auto it = client_sockets.begin(); it != client_sockets.end();) { - BettolaLib::Network::Socket* client = *it; + BettolaLib::Network::TCPSocket* client = *it; if(FD_ISSET(client->get_sockfd(), &read_fds)) { bool client_disconnected = false; while (true) { @@ -110,17 +153,6 @@ int main(void) { if(bytes_received < 0) { 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) { @@ -138,7 +170,7 @@ int main(void) { } /* Broadcase game state. */ - game.broadcast_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));