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.
This commit is contained in:
Ritchie Cunningham 2025-09-14 00:52:18 +01:00
parent 5b30ab67c1
commit a2a8b052af
12 changed files with 339 additions and 114 deletions

View File

@ -6,6 +6,7 @@ namespace BettolaLib {
namespace Network {
struct PlayerPosMessage {
unsigned int player_id;
float x;
float y;
};

View File

@ -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);

View File

@ -0,0 +1,26 @@
#pragma once
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
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. */

View File

@ -7,21 +7,20 @@
#include <fcntl.h>
#include <cerrno>
#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;

View File

@ -0,0 +1,62 @@
#include <cerrno>
#include <cstdio>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#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;
}
}

View File

@ -6,13 +6,13 @@
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_timer.h>
#include <SDL3/SDL_video.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <cstddef>
#include <netinet/in.h>
#include <cstdio>
#include <string>
#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;

View File

@ -1,5 +1,6 @@
#pragma once
#include <netinet/in.h>
#include <SDL3/SDL.h>
#include <vector>
@ -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<RemotePlayer> _remote_players;
BettolaLib::Network::Socket _client_socket;
BettolaLib::Network::TCPSocket _tcp_socket;
BettolaLib::Network::UDPSocket _udp_socket;
sockaddr_in _server_addr;
};

View File

@ -1,12 +1,15 @@
#include <algorithm>
#include <netinet/in.h>
#include <string.h>
#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);
}

View File

@ -1,16 +1,23 @@
#pragma once
#include <netinet/in.h>
#include <vector>
#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<Player*> _players;

View File

@ -1,8 +1,12 @@
#include <string.h>
#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));
}

View File

@ -1,17 +1,23 @@
#pragma once
#include "bettola/network/socket.h"
#include <netinet/in.h>
#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;
};

View File

@ -2,12 +2,14 @@
#include <cstdio>
#include <chrono>
#include <sys/select.h>
#include <cstring>
#include <thread>
#include <vector>
#include <fcntl.h>
#include <signal.h>
#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<BettolaLib::Network::Socket*> client_sockets;
std::vector<BettolaLib::Network::TCPSocket*> 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));