[Add] Client/server networking and 'ls' command.
This commit is contained in:
parent
70f096abc4
commit
92106a3c44
@ -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)
|
||||
|
||||
76
client/src/client_network.cpp
Normal file
76
client/src/client_network.cpp
Normal 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. */
|
||||
}
|
||||
|
||||
17
client/src/client_network.h
Normal file
17
client/src/client_network.h
Normal 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;
|
||||
};
|
||||
@ -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();
|
||||
|
||||
@ -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
26
common/src/vfs.h
Normal 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;
|
||||
};
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
97
server/src/network_manager.cpp
Normal file
97
server/src/network_manager.cpp
Normal 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;
|
||||
}
|
||||
25
server/src/network_manager.h
Normal file
25
server/src/network_manager.h
Normal 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
7
server/src/player.cpp
Normal 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
13
server/src/player.h
Normal 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:
|
||||
};
|
||||
37
server/src/vfs_manager.cpp
Normal file
37
server/src/vfs_manager.cpp
Normal 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
7
server/src/vfs_manager.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "vfs.h"
|
||||
|
||||
namespace vfs_manager {
|
||||
vfs_node* create_root_system(void);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user