feat(server) Added client/server for online play

Note: This is fucking broken! it's too early in the morning. Fix
tomorrow.
This commit is contained in:
Ritchie Cunningham 2025-09-13 05:22:30 +01:00
parent 5a4d4ff7a3
commit 6e935918a3
7 changed files with 316 additions and 4 deletions

View File

@ -23,3 +23,6 @@ add_executable(bettola ${SOURCES})
target_link_libraries(bettola PRIVATE bettola_lib SDL3::SDL3 GLEW::glew OpenGL::GL) target_link_libraries(bettola PRIVATE bettola_lib SDL3::SDL3 GLEW::glew OpenGL::GL)
# Server executable.
add_executable(bettola_server srv/main.cpp)
target_link_libraries(bettola_server PRIVATE bettola_lib)

View File

@ -0,0 +1,9 @@
#pragma once
namespace BettolaLib {
namespace Network {
const unsigned short DEFAULT_PORT = 12345; /* This will do for now. */
} /* namespace Network. */
} /* namespace BettolaLib. */

View File

@ -0,0 +1,37 @@
#pragma once
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string>
#include <cstdio>
namespace BettolaLib {
namespace Network {
class Socket {
public:
Socket(void);
~Socket(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. */
bool connect(const std::string& ip_address, unsigned short port);
ssize_t send(const void* buffer, size_t length);
ssize_t recv(void* buffer, size_t length);
void close(void);
int get_sockfd(void) const { return _sockfd; }
bool is_valid(void) const { return _sockfd != -1; }
private:
int _sockfd;
struct sockaddr_in _address;
};
} /* namespace Network. */
} /* namespace BettolaLib. */

View File

@ -0,0 +1,130 @@
#include <arpa/inet.h>
#include <asm-generic/socket.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include "bettola/network/socket.h"
#include "bettola/network/net_common.h"
namespace BettolaLib {
namespace Network {
Socket::Socket(void) : _sockfd(-1) {
memset(&_address, 0, sizeof(_address));
}
Socket::~Socket(void) {
this->close();
}
bool Socket::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));
perror("Failed to create socket.");
return false;
}
/* Attempt to set socket to blocking mode explicitly??? */
int flags = fcntl(_sockfd, F_GETFL, 0);
if(flags == -1) {
perror("fcntl F_GETFL failed.");
return false;
}
if(fcntl(_sockfd, F_SETFL, flags & ~O_NONBLOCK) == -1) {
perror("fcntl F_SETFL O_NONBLOCK failed");
return false;
}
return true;
}
bool Socket::bind(unsigned short port) {
_address.sin_family = AF_INET;
_address.sin_addr.s_addr = INADDR_ANY;
_address.sin_port = htons(port);
int opt = 1;
if(setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt SO_REUSEADDR failed.");
return false;
}
if(::bind(_sockfd, (struct sockaddr*)&_address, sizeof(_address)) == -1) {
perror("Socket bind failed.");
return false;
}
return true;
}
bool Socket::listen(int backlog) {
if(::listen(_sockfd, backlog) == -1) {
perror("Socket accept failed.");
return false;
}
return true;
}
Socket* Socket::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);
if(client_sockfd == -1) {
perror("Socket accept failed.");
return nullptr;
}
Socket* new_socket = new Socket();
new_socket->_sockfd = client_sockfd;
new_socket->_address = client_addr;
return new_socket;
}
bool Socket::connect(const std::string& ip_address, unsigned short port) {
_address.sin_family = AF_INET;
_address.sin_port = htons(port);
if(inet_pton(AF_INET, ip_address.c_str(), &_address.sin_addr) <= 0) {
perror("Invalid address / Address not supported.");
return false;
}
if(::connect(_sockfd, (struct sockaddr*)&_address, sizeof(_address)) <= 0) {
fprintf(stderr, "Socket::connect() failed for sockfd %d with errno %d: %s\n", _sockfd,
errno, strerror(errno));
perror("Connection failed.");
return false;
}
return true;
}
ssize_t Socket::send(const void* buffer, size_t length) {
ssize_t bytes_sent = ::send(_sockfd, buffer, length, 0);
if(bytes_sent == -1) {
perror("Send failed.");
}
return bytes_sent;
}
ssize_t Socket::recv(void* buffer, size_t length) {
ssize_t bytes_received = ::recv(_sockfd, buffer, length, 0);
if(bytes_received == -1) {
perror("Receive failed.");
}
return bytes_received;
}
void Socket::close(void) {
if(_sockfd != -1) {
::close(_sockfd);
_sockfd = -1;
}
}
} /* namespace Network. */
} /* namespace BettolaLib. */

View File

@ -6,8 +6,11 @@
#include <SDL3/SDL_timer.h> #include <SDL3/SDL_timer.h>
#include <SDL3/SDL_video.h> #include <SDL3/SDL_video.h>
#include <cstdio> #include <cstdio>
#include <string>
#include "bettola.h" #include "bettola.h"
#include "bettola/network/socket.h"
#include "bettola/network/net_common.h"
/* Dacav's resolution ;) */ /* Dacav's resolution ;) */
const int SCREEN_WIDTH = 800; const int SCREEN_WIDTH = 800;
@ -25,6 +28,8 @@ Bettola::~Bettola(void) {
SDL_GL_DestroyContext(_gl_context); SDL_GL_DestroyContext(_gl_context);
} }
_client_socket.close(); /* Explicity close client socket. */
if(_window) { if(_window) {
SDL_DestroyWindow(_window); SDL_DestroyWindow(_window);
} }
@ -32,16 +37,20 @@ Bettola::~Bettola(void) {
SDL_Quit(); SDL_Quit();
if(_gl_context) { if(_gl_context) {
/* Should only be deleted if it was successfully created
* and if _gl_context is valid.
*/
glDeleteVertexArrays(1, &_vao); glDeleteVertexArrays(1, &_vao);
glDeleteBuffers(1, &_vbo); glDeleteBuffers(1, &_vbo);
} }
} }
int Bettola::run(void) { int Bettola::run(void) {
if(!init_sdl()) return -1; if(!init_sdl()) return -1;
if(!create_window()) return -1; if(!create_window()) return -1;
if(!create_gl_context()) return -1; if(!create_gl_context()) return -1;
if(!init_glew()) return -1; if(!init_client_connection()) return -1;
if(!init_glew()) return -1;
if(!_shader.load_from_files("assets/shaders/simple.vert", if(!_shader.load_from_files("assets/shaders/simple.vert",
"assets/shaders/simple.frag")) { "assets/shaders/simple.frag")) {
@ -68,6 +77,18 @@ 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. */
send_data("Player position: " + std::to_string(_player.get_x()) + "," +
std::to_string(_player.get_y()));
std::string response = receive_data();
printf("Bettola Client (in game loop): Received: '%s'\n", response.c_str());
network_time = 0.0;
}
process_events(); process_events();
update(delta_time); update(delta_time);
render(); render();
@ -215,3 +236,44 @@ void Bettola::setup_quad(void) {
glBindVertexArray(0); glBindVertexArray(0);
} }
bool Bettola::init_client_connection(void) {
if(!_client_socket.create()) {
printf("Bettola client: Failed to create socket.\n");
return false;
}
if(!_client_socket.connect("127.0.0.1", BettolaLib::Network::DEFAULT_PORT)) {
perror("Bettola Client: Failed to connect to server.\n");
//_client_socket.close();
return false;
}
printf("Bettola Client: Connected to server at 127.0.01.:%hu\n", BettolaLib::Network::DEFAULT_PORT);
return true;
}
void Bettola::send_data(const std::string& data) {
ssize_t bytes_sent = _client_socket.send(data.c_str(), data.length());
if(bytes_sent == -1) {
perror("Bettola Client: Failed to send data.");
} else {
printf("Bettola Client: Sent %zd bytes: '%s'\n", bytes_sent, data.c_str());
}
}
std::string Bettola::receive_data(void) {
char buffer[256]; /* Just a small buffer currently. */
ssize_t bytes_received = _client_socket.recv(buffer, sizeof(buffer) - 1);
if(bytes_received == -1) {
perror("Bettola Client: Failed to receive data.");
return "";
} else if(bytes_received == 0) {
printf("Bettola Client: Server disconnected.\n");
/* Attempt to reconnect. */
_client_socket.close(); /* Close the old socket. */
return "";
}
buffer[bytes_received] = '\0';
return std::string(buffer);
}

View File

@ -5,6 +5,7 @@
#include "graphics/shader.h" #include "graphics/shader.h"
#include "game/player.h" #include "game/player.h"
#include "math/mat4.h" #include "math/mat4.h"
#include "bettola/network/socket.h"
class Bettola { class Bettola {
public: public:
@ -22,6 +23,11 @@ private:
bool init_glew(void); bool init_glew(void);
bool create_window(void); bool create_window(void);
bool create_gl_context(void); bool create_gl_context(void);
bool init_client_connection(void);
void send_data(const std::string& data);
std::string receive_data(void);
void setup_quad(void); void setup_quad(void);
struct InputState { struct InputState {
@ -43,4 +49,5 @@ private:
BettolaMath::Mat4 _projection; BettolaMath::Mat4 _projection;
Player _player; Player _player;
InputState _input; InputState _input;
BettolaLib::Network::Socket _client_socket;
}; };

64
srv/main.cpp Normal file
View File

@ -0,0 +1,64 @@
#include <cstdio>
#include "bettola/network/socket.h"
#include "bettola/network/net_common.h"
int main(void) {
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);
/* Main server loop. */
while(true) {
BettolaLib::Network::Socket* client_socket = server_socket.accept();
if(client_socket == nullptr) {
printf("Bettola Server: Failed to accept client connection.\n");
continue; /* try accepting again. */
}
printf("Bettola Server: Client connected!\n");
char buffer[256]; /* Small buffer for echo. */
ssize_t bytes_received;
do {
bytes_received = client_socket->recv(buffer, sizeof(buffer) - 1);
if(bytes_received > 0) {
buffer[bytes_received] = '\n';
printf("Bettola Server: Received from client: '%s'\n", buffer);
client_socket->send(buffer, bytes_received); /* Echo back. */
} else if(bytes_received == -1) {
perror("Bettola Server: Error receiving from client.");
}
} while(bytes_received > 0);
/* Let's just keep it connected for now. */
// printf("Bettola Server: Client disconnected.\n");
// client_socket->close();
// delete client_socket;
}
server_socket.close(); /* Shouldn't reach here. */
printf("=== Bettola Server: Shutting Down ===\n");
return 0;
}