From 6e935918a384302390807f6fed8ecb2f1a7c3226 Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Sat, 13 Sep 2025 05:22:30 +0100 Subject: [PATCH] feat(server) Added client/server for online play Note: This is fucking broken! it's too early in the morning. Fix tomorrow. --- CMakeLists.txt | 3 + .../include/bettola/network/net_common.h | 9 ++ libbettola/include/bettola/network/socket.h | 37 +++++ libbettola/src/bettola/network/socket.cpp | 130 ++++++++++++++++++ src/bettola.cpp | 70 +++++++++- src/bettola.h | 7 + srv/main.cpp | 64 +++++++++ 7 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 libbettola/include/bettola/network/net_common.h create mode 100644 libbettola/include/bettola/network/socket.h create mode 100644 libbettola/src/bettola/network/socket.cpp create mode 100644 srv/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 09ab6ca..02c7469 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,3 +23,6 @@ add_executable(bettola ${SOURCES}) 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) diff --git a/libbettola/include/bettola/network/net_common.h b/libbettola/include/bettola/network/net_common.h new file mode 100644 index 0000000..3287276 --- /dev/null +++ b/libbettola/include/bettola/network/net_common.h @@ -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. */ diff --git a/libbettola/include/bettola/network/socket.h b/libbettola/include/bettola/network/socket.h new file mode 100644 index 0000000..5e11439 --- /dev/null +++ b/libbettola/include/bettola/network/socket.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include + +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. */ diff --git a/libbettola/src/bettola/network/socket.cpp b/libbettola/src/bettola/network/socket.cpp new file mode 100644 index 0000000..71c2bbe --- /dev/null +++ b/libbettola/src/bettola/network/socket.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include +#include + +#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. */ diff --git a/src/bettola.cpp b/src/bettola.cpp index 7c93f5b..314c588 100644 --- a/src/bettola.cpp +++ b/src/bettola.cpp @@ -6,8 +6,11 @@ #include #include #include +#include #include "bettola.h" +#include "bettola/network/socket.h" +#include "bettola/network/net_common.h" /* Dacav's resolution ;) */ const int SCREEN_WIDTH = 800; @@ -25,6 +28,8 @@ Bettola::~Bettola(void) { SDL_GL_DestroyContext(_gl_context); } + _client_socket.close(); /* Explicity close client socket. */ + if(_window) { SDL_DestroyWindow(_window); } @@ -32,16 +37,20 @@ Bettola::~Bettola(void) { SDL_Quit(); if(_gl_context) { + /* Should only be deleted if it was successfully created + * and if _gl_context is valid. + */ glDeleteVertexArrays(1, &_vao); glDeleteBuffers(1, &_vbo); } } int Bettola::run(void) { - if(!init_sdl()) return -1; - if(!create_window()) return -1; - if(!create_gl_context()) return -1; - if(!init_glew()) return -1; + if(!init_sdl()) return -1; + if(!create_window()) return -1; + if(!create_gl_context()) return -1; + if(!init_client_connection()) return -1; + if(!init_glew()) return -1; if(!_shader.load_from_files("assets/shaders/simple.vert", "assets/shaders/simple.frag")) { @@ -68,6 +77,18 @@ 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. */ + 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(); update(delta_time); render(); @@ -215,3 +236,44 @@ void Bettola::setup_quad(void) { 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); +} + diff --git a/src/bettola.h b/src/bettola.h index b9e7b9d..d43d909 100644 --- a/src/bettola.h +++ b/src/bettola.h @@ -5,6 +5,7 @@ #include "graphics/shader.h" #include "game/player.h" #include "math/mat4.h" +#include "bettola/network/socket.h" class Bettola { public: @@ -22,6 +23,11 @@ private: bool init_glew(void); bool create_window(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); struct InputState { @@ -43,4 +49,5 @@ private: BettolaMath::Mat4 _projection; Player _player; InputState _input; + BettolaLib::Network::Socket _client_socket; }; diff --git a/srv/main.cpp b/srv/main.cpp new file mode 100644 index 0000000..2bb3c75 --- /dev/null +++ b/srv/main.cpp @@ -0,0 +1,64 @@ +#include + +#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; +}