[Add] Client/server networking and 'ls' command.

This commit is contained in:
Ritchie Cunningham 2025-09-20 18:58:24 +01:00
parent 70f096abc4
commit 92106a3c44
14 changed files with 348 additions and 5 deletions

View File

@ -5,10 +5,13 @@ add_executable(bettolac
)
find_package(SDL3 REQUIRED)
find_package(SDL3_net REQUIRED)
find_package(GLEW REQUIRED)
find_package(OpenGL REQUIRED)
find_package(Freetype REQUIRED)
target_link_libraries(bettolac PRIVATE bettola SDL3::SDL3 GLEW::glew OpenGL::GL Freetype::Freetype)
target_link_libraries(bettolac PRIVATE bettola SDL3::SDL3 SDL3_net
GLEW::glew OpenGL::GL Freetype::Freetype)
target_include_directories(bettolac PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(bettolac PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src
/usr/local/include/SDL3_net)

View File

@ -0,0 +1,76 @@
#include <cstdio>
#include <cstring>
#include "SDL_net.h"
#include "client_network.h"
#include <SDL3/SDL_error.h>
ClientNetwork::ClientNetwork(void) {
if(!NET_Init()) {
printf("Error: NET_Init: %s\n", SDL_GetError());
}
_socket = nullptr;
}
ClientNetwork::~ClientNetwork(void) {
if(_socket) {
NET_DestroyStreamSocket(_socket);
}
NET_Quit();
}
bool ClientNetwork::connect(const char* host, int port) {
NET_Address* address = NET_ResolveHostname(host);
if(!address) {
printf("Error: NET_ResolveHostname %s\n", SDL_GetError());
return false;
}
/* This seems impossible, but I'll try it..
* Pass the port to CreateClient as the compiler insists we do.
*/
/* FUCK ME!! Wait up to 5 seconds for DNS resolution to complete. */
if(NET_WaitUntilResolved(address, 5000) <= 0) {
printf("Error: NET_WaitUntilResolved: %s\n", SDL_GetError());
NET_UnrefAddress(address);
return false;
}
_socket = NET_CreateClient(address, port);
if(!_socket) {
printf("Error: NET_CreateClient: %s\n", SDL_GetError());
NET_UnrefAddress(address);
return false;
}
/* Wait up to 5 seconds for the connection to establish. */
if(NET_WaitUntilConnected((_socket), 5000) > 0) {
printf("connected server.\n");
NET_UnrefAddress(address);
return true;
}
printf("Error: connection timeout.\n");
NET_UnrefAddress(address);
return false;
}
void ClientNetwork::send_command(const char* command) {
if(!_socket) return;
NET_WriteToStreamSocket(_socket, command, strlen(command)+1);
}
std::string ClientNetwork::receive_response(void) {
if(!_socket) return "";
char buffer[2048];
memset(buffer, 0, sizeof(buffer));
void* socket_ptr = _socket;
if(NET_WaitUntilInputAvailable(&socket_ptr, 1, 2000) > 0) {
if(NET_ReadFromStreamSocket(_socket, buffer, sizeof(buffer)) > 0) {
return std::string(buffer);
}
}
return ""; /* Return empty on timeout or error. */
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <SDL_net.h>
#include <string>
class ClientNetwork {
public:
ClientNetwork(void);
~ClientNetwork(void);
bool connect(const char* host, int port);
void send_command(const char* command);
std::string receive_response(void);
private:
NET_StreamSocket* _socket;
};

View File

@ -3,6 +3,7 @@
#include <GL/glew.h>
#include <SDL3/SDL_events.h>
#include "client_network.h"
#include "gfx/txt_renderer.h"
#include "terminal.h"
@ -10,6 +11,16 @@ Terminal::Terminal(void) {
/* Placeholder welcome message to history. */
_history.push_back("Welcome to Bettola");
_scroll_offset = 0;
_network = new ClientNetwork();
if(_network->connect("localhost", 8080)) {
_history.push_back("Connection to server Success!");
} else {
_history.push_back("Connection to server failed!");
}
}
Terminal::~Terminal(void) {
delete _network;
}
void Terminal::_on_ret_press(void) {
@ -20,6 +31,11 @@ void Terminal::_on_ret_press(void) {
* TODO: Parse and execute commands.
*/
printf("Command entered: %s\n", _input_buffer.c_str());
_network->send_command(_input_buffer.c_str());
std::string response = _network->receive_response();
if(!response.empty()) {
_history.push_back(response);
}
if(_input_buffer == "clear") {
_history.clear();

View File

@ -4,10 +4,12 @@
#include <SDL3/SDL.h>
#include "gfx/txt_renderer.h"
#include "client_network.h"
class Terminal {
public:
Terminal(void);
~Terminal(void);
void handle_input(SDL_Event* event);
void render(TextRenderer* renderer, int x, int y, int width, int height, bool show_cursor);
@ -19,4 +21,5 @@ private:
std::string _input_buffer;
std::vector<std::string> _history;
int _scroll_offset;
ClientNetwork* _network;
};

26
common/src/vfs.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <string>
#include <map>
struct vfs_node;
/* Store children for quick lookup by name. */
typedef std::map<std::string, vfs_node*> vfs_child_map;
enum vfs_node_type {
FILE_NODE,
DIR_NODE
};
struct vfs_node {
std::string name;
vfs_node_type type;
/* Files. */
std::string content;
/* Directories. */
vfs_child_map children;
vfs_node* parent;
};

View File

@ -4,5 +4,11 @@ add_executable(bettolas
${BETTOLAS_SOURCES}
)
target_link_libraries(bettolas PRIVATE bettola)
find_package(SDL3 REQUIRED)
find_package(SDL3_net REQUIRED)
target_link_libraries(bettolas PRIVATE bettola SDL3::SDL3 SDL3_net)
target_include_directories(bettolas PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
/usr/local/include/SDL3_net)

View File

@ -1,8 +1,18 @@
#include <cstdio>
#include "bettola.h"
#include "vfs_manager.h"
#include "network_manager.h"
int main(int argc, char** argv) {
printf("=== Server starting ===\n");
bettola_function();
vfs_node* root_vfs = vfs_manager::create_root_system();
printf("Virtual file system created. Root contains '%s' directory.\n",
root_vfs->children["home"]->name.c_str());
NetworkManager* net_manager = new NetworkManager(root_vfs);
net_manager->start(); /* Our loop. */
delete net_manager; /* Shouldn't get here. */
return 0;
}

View File

@ -0,0 +1,97 @@
#include <cstdio>
#include <cstring>
#include "vfs.h"
#include "network_manager.h"
NetworkManager::NetworkManager(vfs_node* vfs_root) {
_vfs_root = vfs_root;
if(!NET_Init()) {
printf("Error: SDLNet_Init: %s\n", SDL_GetError());
return;
}
_server_socket = NET_CreateServer(NULL, 8080);
if(!_server_socket) {
printf("Error: NET_CreateServer: %s\n", SDL_GetError());
return;
}
}
NetworkManager::~NetworkManager(void) {
NET_DestroyServer(_server_socket);
NET_Quit();
}
void NetworkManager::start(void) {
printf("BettolaServer listening on port 8080...\n");
while(true) {
/* Check for and accept any new client connections. */
_handle_new_connections();
/* Check all existing clients for incoming data. */
_handle_client_activity();
/* Let's not be burning CPU cycles. */
SDL_Delay(0);
}
}
void NetworkManager::_handle_new_connections(void) {
NET_StreamSocket* client_socket;
if(NET_AcceptClient(_server_socket, &client_socket)) {
if(client_socket) {
Player* new_player = new Player(client_socket);
new_player->current_dir = _vfs_root;
_players.push_back(new_player);
printf("Client connected. Total players: %zu\n", _players.size());
}
}
}
void NetworkManager::_handle_client_activity(void) {
char buffer [512];
/* Iterate backwards so we can safely remove disconnected clients. */
for(int i = _players.size()-1; i >= 0; --i) {
Player* player = _players[i];
/* NET_ReadFromStreamSocket returns > 0 if data was received.
* 0 if no data, and < 0 on error (disconnect).
*/
int bytes_received = NET_ReadFromStreamSocket(player->socket, buffer, 512);
if(bytes_received > 0) {
_process_command(player, buffer);
} else if(bytes_received < 0 ) {
_disconnect_client(player, i);
}
}
}
void NetworkManager::_process_command(Player* player, char* command) {
/* We'll just do 'ls' for now. */
if(strncmp(command, "ls", 2) == 0) {
std::string response = "";
if(player->current_dir && player->current_dir->type == DIR_NODE) {
for(auto const& [name, node] : player->current_dir->children) {
response += name;
if(node->type == DIR_NODE) {
response += "/";
}
response += "\n";
}
}
/* NET_WriteToStreamSocket is essentially the new send function in SDL3. */
NET_WriteToStreamSocket(player->socket, response.c_str(), response.length()+1);
}
}
void NetworkManager::_disconnect_client(Player* player, int index) {
printf("Client disconnected.\n");
NET_DestroyStreamSocket(player->socket);
_players.erase(_players.begin() + index);
delete player;
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <vector>
#include <SDL3/SDL.h>
#include "SDL_net.h"
#include "player.h"
class NetworkManager {
public:
NetworkManager(vfs_node* vfs_root);
~NetworkManager(void);
void start(void);
private:
void _handle_new_connections(void);
void _handle_client_activity(void);
void _process_command(Player* player, char* command);
void _disconnect_client(Player* player, int index);
NET_Server* _server_socket;
std::vector<Player*> _players;
vfs_node* _vfs_root;
};

7
server/src/player.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "player.h"
#include "SDL_net.h"
Player::Player(NET_StreamSocket* new_socket) {
socket = new_socket;
current_dir = nullptr; /* Will set to VFS root on connection. */
}

13
server/src/player.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <SDL_net.h>
#include "vfs.h"
class Player {
public:
Player(NET_StreamSocket* socket);
NET_StreamSocket* socket;
vfs_node* current_dir;
private:
};

View File

@ -0,0 +1,37 @@
#include "vfs_manager.h"
#include "vfs.h"
/* Create a new node. */
vfs_node* new_node(std::string name, vfs_node_type type, vfs_node* parent) {
vfs_node* node = new vfs_node();
node->name = name;
node->type = type;
node->parent = parent;
return node;
}
vfs_node* vfs_manager::create_root_system(void) {
/* Rood directory. */
vfs_node* root = new_node("/", DIR_NODE, nullptr);
/* Subdirectories. */
vfs_node* home = new_node("home", DIR_NODE, root);
vfs_node* bin = new_node("bin", DIR_NODE, root);
root->children["home"] = home;
root->children["bin"] = bin;
/* User diractory. */
vfs_node* user = new_node("user", DIR_NODE, home);
home->children["user"] = user;
/* Create file. */
vfs_node* readme = new_node("readme.txt", FILE_NODE, user);
readme->content = "Welcome to your new virtual machine.";
user->children["readme.txt"] = readme;
vfs_node* ls_exe = new_node("ls", FILE_NODE, bin);
ls_exe->content = "[executable]";
bin->children["ls"] = ls_exe;
return root;
}

7
server/src/vfs_manager.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include "vfs.h"
namespace vfs_manager {
vfs_node* create_root_system(void);
}