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:
parent
5b30ab67c1
commit
a2a8b052af
@ -6,6 +6,7 @@ namespace BettolaLib {
|
|||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
struct PlayerPosMessage {
|
struct PlayerPosMessage {
|
||||||
|
unsigned int player_id;
|
||||||
float x;
|
float x;
|
||||||
float y;
|
float y;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,15 +9,15 @@
|
|||||||
namespace BettolaLib {
|
namespace BettolaLib {
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
class Socket {
|
class TCPSocket {
|
||||||
public:
|
public:
|
||||||
Socket(void);
|
TCPSocket(void);
|
||||||
~Socket(void);
|
~TCPSocket(void);
|
||||||
|
|
||||||
bool create(void);
|
bool create(void);
|
||||||
bool bind(unsigned short port);
|
bool bind(unsigned short port);
|
||||||
bool listen(int backlog = 5);
|
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);
|
bool connect(const std::string& ip_address, unsigned short port);
|
||||||
|
|
||||||
ssize_t send(const void* buffer, size_t length);
|
ssize_t send(const void* buffer, size_t length);
|
||||||
26
libbettola/include/bettola/network/udpsocket.h
Normal file
26
libbettola/include/bettola/network/udpsocket.h
Normal 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. */
|
||||||
@ -7,21 +7,20 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
|
||||||
#include "bettola/network/socket.h"
|
#include "bettola/network/tcpsocket.h"
|
||||||
#include "bettola/network/net_common.h"
|
|
||||||
|
|
||||||
namespace BettolaLib {
|
namespace BettolaLib {
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
Socket::Socket(void) : _sockfd(-1) {
|
TCPSocket::TCPSocket(void) : _sockfd(-1) {
|
||||||
memset(&_address, 0, sizeof(_address));
|
memset(&_address, 0, sizeof(_address));
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket::~Socket(void) {
|
TCPSocket::~TCPSocket(void) {
|
||||||
this->close();
|
this->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Socket::create(void) {
|
bool TCPSocket::create(void) {
|
||||||
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if(_sockfd == -1) {
|
if(_sockfd == -1) {
|
||||||
fprintf(stderr, "Socket::create() failed with errno %d: %s\n", errno, strerror(errno));
|
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_family = AF_INET;
|
||||||
_address.sin_addr.s_addr = INADDR_ANY;
|
_address.sin_addr.s_addr = INADDR_ANY;
|
||||||
_address.sin_port = htons(port);
|
_address.sin_port = htons(port);
|
||||||
@ -57,7 +56,7 @@ bool Socket::bind(unsigned short port) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Socket::listen(int backlog) {
|
bool TCPSocket::listen(int backlog) {
|
||||||
if(::listen(_sockfd, backlog) == -1) {
|
if(::listen(_sockfd, backlog) == -1) {
|
||||||
perror("Socket accept failed.");
|
perror("Socket accept failed.");
|
||||||
return false;
|
return false;
|
||||||
@ -65,7 +64,7 @@ bool Socket::listen(int backlog) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket* Socket::accept(void) {
|
TCPSocket* TCPSocket::accept(void) {
|
||||||
struct sockaddr_in client_addr;
|
struct sockaddr_in client_addr;
|
||||||
socklen_t client_len = sizeof(client_addr);
|
socklen_t client_len = sizeof(client_addr);
|
||||||
int client_sockfd = ::accept(_sockfd, (struct sockaddr*)&client_addr, &client_len);
|
int client_sockfd = ::accept(_sockfd, (struct sockaddr*)&client_addr, &client_len);
|
||||||
@ -75,13 +74,13 @@ Socket* Socket::accept(void) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket* new_socket = new Socket();
|
TCPSocket* new_socket = new TCPSocket();
|
||||||
new_socket->_sockfd = client_sockfd;
|
new_socket->_sockfd = client_sockfd;
|
||||||
new_socket->_address = client_addr;
|
new_socket->_address = client_addr;
|
||||||
return new_socket;
|
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_family = AF_INET;
|
||||||
_address.sin_port = htons(port);
|
_address.sin_port = htons(port);
|
||||||
|
|
||||||
@ -104,7 +103,7 @@ bool Socket::connect(const std::string& ip_address, unsigned short port) {
|
|||||||
return true;
|
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);
|
ssize_t bytes_sent = ::send(_sockfd, buffer, length, 0);
|
||||||
if(bytes_sent == -1) {
|
if(bytes_sent == -1) {
|
||||||
perror("Send failed.");
|
perror("Send failed.");
|
||||||
@ -112,7 +111,7 @@ ssize_t Socket::send(const void* buffer, size_t length) {
|
|||||||
return bytes_sent;
|
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);
|
ssize_t bytes_received = ::recv(_sockfd, buffer, length, 0);
|
||||||
if(bytes_received == -1 && errno != EWOULDBLOCK && errno != EAGAIN) {
|
if(bytes_received == -1 && errno != EWOULDBLOCK && errno != EAGAIN) {
|
||||||
perror("Receive failed.");
|
perror("Receive failed.");
|
||||||
@ -120,7 +119,7 @@ ssize_t Socket::recv(void* buffer, size_t length) {
|
|||||||
return bytes_received;
|
return bytes_received;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Socket::close(void) {
|
void TCPSocket::close(void) {
|
||||||
if(_sockfd != -1) {
|
if(_sockfd != -1) {
|
||||||
::close(_sockfd);
|
::close(_sockfd);
|
||||||
_sockfd = -1;
|
_sockfd = -1;
|
||||||
62
libbettola/src/bettola/network/udpsocket.cpp
Normal file
62
libbettola/src/bettola/network/udpsocket.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/bettola.cpp
157
src/bettola.cpp
@ -6,13 +6,13 @@
|
|||||||
#include <SDL3/SDL_stdinc.h>
|
#include <SDL3/SDL_stdinc.h>
|
||||||
#include <SDL3/SDL_timer.h>
|
#include <SDL3/SDL_timer.h>
|
||||||
#include <SDL3/SDL_video.h>
|
#include <SDL3/SDL_video.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <cstddef>
|
#include <netinet/in.h>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "bettola.h"
|
#include "bettola.h"
|
||||||
#include "bettola/network/socket.h"
|
|
||||||
#include "bettola/network/net_common.h"
|
#include "bettola/network/net_common.h"
|
||||||
#include "math/mat4.h"
|
#include "math/mat4.h"
|
||||||
#include "network/message.h"
|
#include "network/message.h"
|
||||||
@ -29,14 +29,19 @@ Bettola::Bettola(void) :
|
|||||||
_gl_context(nullptr),
|
_gl_context(nullptr),
|
||||||
_vao(0),
|
_vao(0),
|
||||||
_vbo(0),
|
_vbo(0),
|
||||||
_our_player_id(0) {}
|
_our_player_id(0) {
|
||||||
|
|
||||||
|
memset(&_server_addr, 0, sizeof(_server_addr));
|
||||||
|
}
|
||||||
|
|
||||||
Bettola::~Bettola(void) {
|
Bettola::~Bettola(void) {
|
||||||
if(_gl_context) {
|
if(_gl_context) {
|
||||||
SDL_GL_DestroyContext(_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) {
|
if(_window) {
|
||||||
SDL_DestroyWindow(_window);
|
SDL_DestroyWindow(_window);
|
||||||
@ -85,47 +90,8 @@ int Bettola::run(void) {
|
|||||||
delta_time = (double)(current_counter-last_count) / (double)perf_freq;
|
delta_time = (double)(current_counter-last_count) / (double)perf_freq;
|
||||||
last_count = current_counter;
|
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_events();
|
||||||
|
process_network();
|
||||||
update(delta_time);
|
update(delta_time);
|
||||||
render();
|
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) {
|
void Bettola::update(double dt) {
|
||||||
float dir_x = 0.0f;
|
float dir_x = 0.0f;
|
||||||
float dir_y = 0.0f;
|
float dir_y = 0.0f;
|
||||||
@ -167,6 +197,25 @@ void Bettola::update(double dt) {
|
|||||||
|
|
||||||
_player.update(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 char window_title[256];
|
||||||
static double time_since_title_update = 0.0;
|
static double time_since_title_update = 0.0;
|
||||||
|
|
||||||
@ -315,24 +364,34 @@ void Bettola::setup_quad(void) {
|
|||||||
|
|
||||||
bool Bettola::init_client_connection(void) {
|
bool Bettola::init_client_connection(void) {
|
||||||
SDL_Delay(1000); /* Wait a second before trying to connect. */
|
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");
|
printf("Bettola client: Failed to create socket.\n");
|
||||||
return false;
|
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");
|
perror("Bettola Client: Failed to connect to server.\n");
|
||||||
//_client_socket.close();
|
|
||||||
return false;
|
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. */
|
/* 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) {
|
if(flags == -1) {
|
||||||
perror("fcntl F_GETFL failed.");
|
perror("fcntl F_GETFL failed.");
|
||||||
return false;
|
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);
|
printf("Bettola Client: Connected to server at 127.0.01.:%hu\n", BettolaLib::Network::DEFAULT_PORT);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -7,8 +8,9 @@
|
|||||||
#include "game/player.h"
|
#include "game/player.h"
|
||||||
#include "game/remote_player.h"
|
#include "game/remote_player.h"
|
||||||
#include "math/mat4.h"
|
#include "math/mat4.h"
|
||||||
#include "bettola/network/socket.h"
|
#include "network/tcpsocket.h"
|
||||||
#include "network/game_state_message.h"
|
#include "network/udpsocket.h"
|
||||||
|
namespace BettolaLib { namespace Network { struct GameStateMessage; } }
|
||||||
|
|
||||||
class Bettola {
|
class Bettola {
|
||||||
public:
|
public:
|
||||||
@ -19,6 +21,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void process_events(void);
|
void process_events(void);
|
||||||
|
void process_network(void);
|
||||||
|
void process_udp_messages(void);
|
||||||
void update(double dt);
|
void update(double dt);
|
||||||
void render(void);
|
void render(void);
|
||||||
void update_remote_players(const BettolaLib::Network::GameStateMessage& msg);
|
void update_remote_players(const BettolaLib::Network::GameStateMessage& msg);
|
||||||
@ -53,5 +57,7 @@ private:
|
|||||||
Player _player;
|
Player _player;
|
||||||
InputState _input;
|
InputState _input;
|
||||||
std::vector<RemotePlayer> _remote_players;
|
std::vector<RemotePlayer> _remote_players;
|
||||||
BettolaLib::Network::Socket _client_socket;
|
BettolaLib::Network::TCPSocket _tcp_socket;
|
||||||
|
BettolaLib::Network::UDPSocket _udp_socket;
|
||||||
|
sockaddr_in _server_addr;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <netinet/in.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "network/game_state_message.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/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));
|
_players.push_back(new Player(socket));
|
||||||
return _players.back();
|
return _players.back();
|
||||||
}
|
}
|
||||||
@ -24,17 +27,25 @@ void Game::remove_player(unsigned int player_id) {
|
|||||||
_players.end());
|
_players.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::update_player_pos(unsigned int player_id, float x, float y) {
|
void Game::handle_udp_message(const BettolaLib::Network::MessageHeader& header,
|
||||||
auto it = std::find_if(_players.begin(), _players.end(),
|
const char* buffer, const sockaddr_in& from_addr) {
|
||||||
[player_id](const Player* player) {
|
|
||||||
return player->get_id() == player_id;
|
if(header.type == BettolaLib::Network::MessageType::PlayerPosition) {
|
||||||
});
|
BettolaLib::Network::PlayerPosMessage msg;
|
||||||
if(it != _players.end()) {
|
memcpy(&msg, buffer, sizeof(msg));
|
||||||
(*it)->set_position(x, y);
|
|
||||||
|
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;
|
BettolaLib::Network::GameStateMessage msg;
|
||||||
msg.num_players = _players.size();
|
msg.num_players = _players.size();
|
||||||
memset(msg.players, 0, sizeof(msg.players));
|
memset(msg.players, 0, sizeof(msg.players));
|
||||||
@ -50,13 +61,14 @@ void Game::broadcast_game_state(void) {
|
|||||||
header.size = sizeof(msg);
|
header.size = sizeof(msg);
|
||||||
|
|
||||||
for(const auto& player : _players) {
|
for(const auto& player : _players) {
|
||||||
BettolaLib::Network::Socket& socket = player->get_socket();
|
if(player->has_udp_addr()) {
|
||||||
socket.send(&header, sizeof(header));
|
udp_socket.send_to(&header, sizeof(header), player->get_udp_addr());
|
||||||
socket.send(&msg, sizeof(msg));
|
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(),
|
auto it = std::find_if(_players.begin(), _players.end(),
|
||||||
[socket](const Player* player) {
|
[socket](const Player* player) {
|
||||||
return &player->get_socket() == socket;
|
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);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "bettola/network/socket.h"
|
#include "bettola/network/tcpsocket.h"
|
||||||
|
#include "bettola/network/udpsocket.h"
|
||||||
|
#include "network/message.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
|
|
||||||
class Game {
|
class Game {
|
||||||
public:
|
public:
|
||||||
Player* add_player(BettolaLib::Network::Socket* socket);
|
Player* add_player(BettolaLib::Network::TCPSocket* socket);
|
||||||
void remove_player(unsigned int player_id);
|
void remove_player(unsigned int player_id);
|
||||||
void update_player_pos(unsigned int player_id, float x, float y);
|
|
||||||
void broadcast_game_state(void);
|
void handle_udp_message(const BettolaLib::Network::MessageHeader& header,
|
||||||
Player* get_player_by_socket(BettolaLib::Network::Socket* socket);
|
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:
|
private:
|
||||||
std::vector<Player*> _players;
|
std::vector<Player*> _players;
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "network/socket.h"
|
#include "network/tcpsocket.h"
|
||||||
|
|
||||||
unsigned int Player::_next_player_id = 0;
|
unsigned int Player::_next_player_id = 0;
|
||||||
|
|
||||||
Player::Player(BettolaLib::Network::Socket* socket) :
|
Player::Player(BettolaLib::Network::TCPSocket* socket) :
|
||||||
_id(_next_player_id++), _x(0.0f), _y(0.0f), _socket(socket) {}
|
_id(_next_player_id++), _x(0.0f), _y(0.0f), _socket(socket), _has_udp_addr(false) {
|
||||||
|
memset(&_udp_addr, 0, sizeof(_udp_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "bettola/network/socket.h"
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#include "bettola/network/tcpsocket.h"
|
||||||
|
|
||||||
class Player {
|
class Player {
|
||||||
public:
|
public:
|
||||||
Player(BettolaLib::Network::Socket* socket);
|
Player(BettolaLib::Network::TCPSocket* socket);
|
||||||
|
|
||||||
void set_position(float x, float y) { _x = x; _y = y; }
|
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; }
|
unsigned int get_id(void) const { return _id; }
|
||||||
float get_x(void) const { return _x; }
|
float get_x(void) const { return _x; }
|
||||||
float get_y(void) const { return _y; }
|
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:
|
private:
|
||||||
static unsigned int _next_player_id;
|
static unsigned int _next_player_id;
|
||||||
@ -19,5 +25,7 @@ private:
|
|||||||
unsigned int _id;
|
unsigned int _id;
|
||||||
float _x;
|
float _x;
|
||||||
float _y;
|
float _y;
|
||||||
BettolaLib::Network::Socket* _socket;
|
BettolaLib::Network::TCPSocket* _socket;
|
||||||
|
sockaddr_in _udp_addr;
|
||||||
|
bool _has_udp_addr;
|
||||||
};
|
};
|
||||||
|
|||||||
70
srv/main.cpp
70
srv/main.cpp
@ -2,12 +2,14 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <sys/select.h>
|
#include <sys/select.h>
|
||||||
|
#include <cstring>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <signal.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/net_common.h"
|
||||||
#include "bettola/network/message.h"
|
#include "bettola/network/message.h"
|
||||||
#include "bettola/network/player_pos_message.h"
|
#include "bettola/network/player_pos_message.h"
|
||||||
@ -15,14 +17,16 @@
|
|||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
Game game;
|
Game game;
|
||||||
std::vector<BettolaLib::Network::Socket*> client_sockets;
|
std::vector<BettolaLib::Network::TCPSocket*> client_sockets;
|
||||||
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
printf("=== Bettola Server: Starting ===\n");
|
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()) {
|
if(!server_socket.create()) {
|
||||||
printf("Bettola Server: Failed to create socket.\n");
|
printf("Bettola Server: Failed to create socket.\n");
|
||||||
return 1;
|
return 1;
|
||||||
@ -41,7 +45,22 @@ int main(void) {
|
|||||||
return 1;
|
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. */
|
/* Set the server socket to non-blocking. */
|
||||||
int flags = fcntl(server_socket.get_sockfd(), F_GETFL, 0);
|
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);
|
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();
|
auto last_time = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
/* Main server loop. */
|
/* Main server loop. */
|
||||||
@ -58,7 +80,9 @@ int main(void) {
|
|||||||
fd_set read_fds;
|
fd_set read_fds;
|
||||||
FD_ZERO(&read_fds);
|
FD_ZERO(&read_fds);
|
||||||
FD_SET(server_socket.get_sockfd(), &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) {
|
for(const auto& client : client_sockets) {
|
||||||
FD_SET(client->get_sockfd(), &read_fds);
|
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);
|
int activity = select(max_fd+1, &read_fds, nullptr, nullptr, &tv);
|
||||||
|
|
||||||
if(activity > 0) {
|
if(activity > 0) {
|
||||||
|
/* Handle new TCP connections. */
|
||||||
if(FD_ISSET(server_socket.get_sockfd(), &read_fds)) {
|
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) {
|
if(client_socket != nullptr) {
|
||||||
Player* new_player = game.add_player(client_socket);
|
Player* new_player = game.add_player(client_socket);
|
||||||
printf("Bettola Server: Client connected! Player ID: %u\n", new_player->get_id());
|
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();) {
|
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)) {
|
if(FD_ISSET(client->get_sockfd(), &read_fds)) {
|
||||||
bool client_disconnected = false;
|
bool client_disconnected = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -110,17 +153,6 @@ int main(void) {
|
|||||||
if(bytes_received < 0) {
|
if(bytes_received < 0) {
|
||||||
break;
|
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) {
|
if(client_disconnected) {
|
||||||
@ -138,7 +170,7 @@ int main(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Broadcase game state. */
|
/* Broadcase game state. */
|
||||||
game.broadcast_game_state();
|
game.broadcast_game_state(udp_socket);
|
||||||
|
|
||||||
/* Sleep for a short time to avoid busy-waiting. */
|
/* Sleep for a short time to avoid busy-waiting. */
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user