3
\$\begingroup\$

I am new to Rust, so I created a mini library/wrapper for creating multi-cliented Tcp Servers. This is a starter project for me to learn Rust.

How the module works is a user can pass in a function or closure to the start fn to use to handle each new client's connection, the function has to accept HandleClientType as a parameter, which is a type alias for (TcpStream, BubbleServer), so a tuple that contains the newly connected TcpStream and a reference to the server that is calling this function.

I'd like to know how I could improve it's performance and improving it's overall design (some details in the comments) and if there is anyways to simplify my code.

If you wanted to compile this for yourself, a full crate for the module and the main example (also a crate for the TCP client) is provided here

The Module in question:

buble_host/mod.rs:

use std::io;
use std::io::{Error, ErrorKind};
use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
use std::sync::mpsc::{channel, Sender};
use std::sync::*;
use std::thread;
use std::vec::Vec;

#[cfg(feature = "bubble_dbg")]
macro_rules! bubble_print{
    ($($args:expr), *)=> {println!($($args), *);}

}
#[cfg(not(feature = "bubble_dbg"))]
macro_rules! bubble_print {
    ($($args:expr), *) => {};
}

//Events for debugging and getting information from the server.
pub enum ServerEvent {
    None,
    Disconnection(SocketAddr),
}
type ErrorSender = Option<Sender<ServerEvent>>;
//Clients List Thread Safe
pub type ClientsListTS = Arc<Mutex<Vec<TcpStream>>>;
pub type ClientsVec = Vec<TcpStream>;
pub type HandleClientCallBack = dyn Fn(HandleClientType) + Send + Sync + 'static;

//Helper struct
struct ClientsList(ClientsListTS);
impl ClientsList {
    pub fn new() -> ClientsListTS {
        Arc::new(Mutex::new(Vec::<TcpStream>::new()))
    }
}

///Data that is sent to user provided FnMut `set in fn start` when a client connects to a started(fn start) server
pub type HandleClientType<'a> = (&'a mut TcpStream, &'a mut BubbleServer);

#[derive(Clone)]
pub struct BubbleServer {
    ip: String,
    error_sender: ErrorSender,
    clients: ClientsListTS,
}

impl BubbleServer {
    pub fn new(ip: String) -> Self {
        BubbleServer {
            ip,
            clients: ClientsList::new(),
            error_sender: None,
        }
    }

    fn get_sock_index(
        clients: &ClientsVec,
        socket_addr: &std::net::SocketAddr,
    ) -> io::Result<usize> {
        clients
            .iter()
            .position(|x| &x.peer_addr().unwrap() == socket_addr)
            .ok_or_else(|| {
                Error::new(
                    ErrorKind::InvalidInput,
                    format!("Could not find socket address: '{}' ", socket_addr),
                )
            })
    }
    /// used to retrieve socket from clients by ip address
    /// # Example :
    /// ```
    /// let server = BubbleServer::new(String::from("localhost:25568"));
    /// let addr_to_find: SocketAddr = "127.0.0.1:25565"
    ///     .parse()
    ///     .expect("Could not parse ip address!");
    /// let socket = server.get_sock(&addr_to_find);
    /// assert!(socket.is_none());
    ///
    /// ```
    ///
    #[allow(dead_code)]
    pub fn get_sock(&self, socket_addr: &SocketAddr) -> Option<TcpStream> {
        let clients = self.get_clients().ok()?;
        let clients = clients.lock().ok()?;
        if let Ok(index) = BubbleServer::get_sock_index(&clients, socket_addr) {
            Some(clients[index].try_clone().unwrap())
        } else {
            None
        }
    }

    ///`handle_socket_disconnection` will shutdown the socket, then send a disconnection event to the error_sender.
    fn handle_socket_disconnection(&self, socket_: &TcpStream) -> io::Result<()> {
        self.remove_socket(&socket_)?;
        let sock_addr = socket_.peer_addr().unwrap();

        socket_
            .shutdown(Shutdown::Both)
            .expect("Could not shutdown stream..");
        let event = ServerEvent::Disconnection(sock_addr);
        let sender = self.error_sender.as_ref();
        if let Some(sender) = sender {
            sender
                .send(event)
                .unwrap_or_else(|e| println!("error sending to error_sender {}", e));
        }
        bubble_print!("Removed client {}", &sock_addr.to_string());
        Ok(())
    }

    /// `get_clients` Returns clients list reference using Arc;
    pub fn get_clients(&self) -> io::Result<ClientsListTS> {
        Ok(Arc::clone(&self.clients))
    }
    #[allow(dead_code)]
    pub fn set_on_event<F: Fn(ServerEvent) + Send + Sync + 'static>(&self, callback: F) {
        let (err_send, err_rec) = channel::<ServerEvent>();
        self.error_sender.as_ref().get_or_insert(&err_send);
        let call = Arc::new(callback);
        std::thread::spawn(move || loop {
            let event = err_rec.recv().unwrap_or(ServerEvent::None);
            call(event);
        });
    }
    ///
    ///####`remove_socket` Removes given TcpStream from server's `clients`
    ///
    /// * Locks `clients` vec (Vec<TcpStream>)
    /// * Removes `socket` from clients list.
    fn remove_socket(&self, socket: &TcpStream) -> io::Result<()> {
        //Is this the least verbose way to dereference this?
        let clients = self.get_clients()?;
        let mut clients = clients
            .lock()
            .map_err(|err| io::Error::new(ErrorKind::Other, err.to_string()))?;
        //

        let socket_addr = socket.peer_addr()?;

        let index = BubbleServer::get_sock_index(&clients, &socket_addr)?;
        clients.remove(index);
        Ok(())
    }
    ///Runs the user's defined function in a new thread passing in the newly connected socket.
    fn handle_client(
        &self,
        socket: &mut TcpStream,
        handle_client: &'static HandleClientCallBack,
    ) -> io::Result<()> {
        let mut socket_ = socket.try_clone()?;
        //Again, I hate this cloning
        let mut _self = self.clone();
        //Need to clone provided function
        let handle_client_func = handle_client.clone();
        thread::spawn(move || {
            
            handle_client_func((&mut socket_, &mut _self));

            _self
                .handle_socket_disconnection(&socket_)
                .unwrap_or_else(|e| {
                    println!("Error in handling socket disconnection, Err: '{}' ", e);
                });
        });
        Ok(())
    }
    ///Locks `clients` (Vec<TcpStream>) and adds `socket` to Vec
    fn add_client(&self, socket: TcpStream) {
        self.clients.lock().unwrap().push(socket);
    }

    /// Continously accepts incoming connections
    /// * Is non blocking.
    /// * Locks clients list when user connects
    pub fn start(&self, handle_client_cb: &'static HandleClientCallBack) -> io::Result<()> {
        let ip = &self.ip;
        bubble_print!("server with  ip of {} has started...", ip);
        let socket_acceptor = TcpListener::bind(ip).expect("Failed to initialize server...");

        //I'm not a fan of cloning the server struct, but I didn't want the  user to  have the potential to  deadlock the server
        //without explicitily locking mutex's in their own code.
        //for example it should be obvious that this will deadlock when a user connects
        // ```
        // {
        //     let server = BubbleServer::new(String::from("localhost:25568"));
        //     let clients = server.get_clients().expect("could not get clients!");
        //     let clients = clients.lock().unwrap();
        //     server.start(&|data: HandleClientType| {
        //         println!("new connection! dropping now!");
        //     });
    
        //     thread::park();
        // }
        // ```
        let self_ = self.clone();

        // Accept new incoming connections
        thread::spawn(move || loop {
            let h_cb_clone = handle_client_cb.clone();
            println!("waiting for next client..");
            if let Ok((mut socket, _)) = socket_acceptor.accept() {
                //Add Client to the clients vector
                self_.add_client(
                    socket
                        .try_clone()
                        .expect("Could not clone socket before handling socket.."),
                );
                //Run user's implementation on how to deal with new client
                self_
                    .handle_client(&mut socket, h_cb_clone)
                    .unwrap_or_else(|e| {
                        println!("Error in handle_client : {}", e);
                    });
            }
            // Could provide implementation for an erroneous accept, and send that to the error_sender.
            // but if many clients were to make erroneous connections this might just clutter the error_sender's receive.
        });
        Ok(())
    }
}

main.rs shows an example of how I create a TextChat server with this module

main.rs:

mod bubble_host;
use crate::bubble_host::{BubbleServer, HandleClientType, ServerEvent};
use std::io;
use std::io::prelude::*;
use std::io::{Error, ErrorKind};
use std::net::TcpStream;
use std::thread;

///Example implementation of BubbleServer

trait TextChatServer {
    fn broad_cast_msg(&mut self, msg: &[u8]) -> Result<(), std::io::Error>;
}
impl TextChatServer for BubbleServer {
    fn broad_cast_msg(&mut self, msg: &[u8]) -> Result<(), std::io::Error> {
        let clients = self.get_clients()?;
        let clients = clients
            .lock()
            .map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;

        for mut client in clients.iter() {
            if let Err(e) = client.write_all(&msg) {
                println!(
                    "Error {} for {}",
                    e,
                    client.peer_addr().unwrap().to_string()
                )
            }
        }
        Ok(())
    }
}
fn read_stream(stream: &mut TcpStream, recv_buf: &mut [u8], read_bytes: &mut usize) -> usize {
    *read_bytes = match stream.read(recv_buf) {
        Ok(read_bytes) => read_bytes,
        Err(_) => 0,
    };
    *read_bytes
}
trait TcpHelper {
    fn send_str(&mut self, msg: &str) -> io::Result<()>;
}
impl TcpHelper for TcpStream {
    fn send_str(&mut self, msg: &str) -> io::Result<()> {
        let data = msg.as_bytes();
        self.write_all(&data)?;
        Ok(())
    }
}
//This Function will be called and ran in a new thread every time a new user connects.
fn handle_new_text_client(client_data: HandleClientType) {
    // HandleClientType provides the stream that is being handled and the server it's being called from so the user could access functions from the server
    let buffer_size = 1000;
    let (stream, server_reference) = client_data;
    println!(
        "Client connected!! ip {}",
        stream.peer_addr().unwrap().to_string()
    );
    stream
        .send_str(&stream.peer_addr().unwrap().to_string())
        .unwrap_or_else(|err| {
            println!(
                "err writing ip to stream, ip {}, err :{}",
                &stream.peer_addr().unwrap().to_string(),
                err
            )
        });
    let mut recv_buf = vec![0u8; buffer_size];
    let mut bytes_read: usize = 0;
    while read_stream(stream, &mut recv_buf, &mut bytes_read) > 0 {
        if let Err(e) = server_reference.broad_cast_msg(&recv_buf[..bytes_read]) {
            println!("Error: {}", e);
        };
    }
}

fn main() {
    let server = BubbleServer::new(String::from("localhost:25568"));
    //Receive events from Server 
    server.set_on_event(|event: ServerEvent| match event {
        ServerEvent::Disconnection(socket_addr) => println!("Disconnection from {}", socket_addr),
        _ => (),
    });
    if let Err(e) = server.start(&handle_new_text_client) {
        println!("Error starting server: {}", e);
    };
    //Blocks this thread here as server.start runs in another thread
    thread::park();
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Your code seems to be pretty solid and following best practices, so these are going to be fairly nit-picky.

Use consistent spacing

You seem to be using rustfmt, so that's good. However, main.rs appears to be using tabs and bubble_host is using tabs. It's fine to use either, but don't mix and match them.

Additionally, markdown headings typically have spacing after them. So instead of:

///
///####`remove_socket` Removes given TcpStream from server's `clients`
///
/// * Locks `clients` vec (Vec<TcpStream>)
/// * Removes `socket` from clients list.

In fact, that doesn't even render correctly when using docs.rs: it shows a verbatim ####. Instead, write:

/// #### `remove_socket` Removes given TcpStream from server's `clients`
///
/// * Locks `clients` vec (Vec<TcpStream>)
/// * Removes `socket` from clients list.

However, typically, you don't begin with the function name in its own documentation.

Link in your docs

When building your documentation with a nightly compiler, as docs.rs does, simply placing code tags in brackets will automatically create a link to it. So if your docs look like:

This is a description that mentions `TcpStream`

You can change it to:

This is a description that mentions [`TcpStream`]

And a link will automatically be created in the generated docs. If the type isn't available locally, or you'd like to describe it with different words, you can do:

If `TcpStream` is at the root level:
[`TcpStream`][crate::TcpStream]
Describe it with [other words][crate::TcpStream] that aren't code

Use the log crate

Instead of

#[cfg(feature = "bubble_dbg")]
macro_rules! bubble_print{
    ($($args:expr), *)=> {println!($($args), *);}

}
#[cfg(not(feature = "bubble_dbg"))]
macro_rules! bubble_print {
    ($($args:expr), *) => {};
}

bubble_print!("Removed client {}", &sock_addr.to_string());

Use the debug! macro (or another macro corresponding to your log level):

debug!("Removed client {}", &sock_addr.to_string());

That way, end users can choose to change log levels and enable/disable logging at runtime in a standard manner.

Run cargo clippy

clippy helps fix some problems in your code, so let's take a look at what it suggests.

methods called new usually return Self

warning: methods called `new` usually return `Self`
  --> src/bubble_host.rs:33:5
   |
33 | /     pub fn new() -> ClientsListTS {
34 | |         Arc::new(Mutex::new(Vec::<TcpStream>::new()))
35 | |     }
   | |_____^
   |
   = note: `#[warn(clippy::new_ret_no_self)]` on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self

Looking at that code:

pub type ClientsListTS = Arc<Mutex<Vec<TcpStream>>>;

//Helper struct
struct ClientsList(ClientsListTS);
impl ClientsList {
    pub fn new() -> ClientsListTS {
        Arc::new(Mutex::new(Vec::<TcpStream>::new()))
    }
}

That's a bit unusual. In fact, you don't need that at all! The Default trait can be derived manually, but many things derive it automatically. Specifically, Arc implements it if what it holds does. Mutex implements it if what it holds does. So, does Vec implement Default? Sure does! So instead of

BubbleServer {
    ip,
    clients: ClientsList::new(),
    error_sender: None,
}

You can write

BubbleServer {
    ip,
    clients: Default::default(),
    error_sender: None,
}

And get rid of ClientsList entirely. However, even if that wasn't available, you still shouldn't use that pattern—either make ClientsList a wrapper struct that you actually use, or create a new freestanding function that creates a new ClientsListTS.

writing &Vec<_> instead of &[_] involves one more reference and cannot be used with non-Vec-based slices.

warning: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices.
  --> src/bubble_host.rs:58:18
   |
58 |         clients: &ClientsVec,
   |                  ^^^^^^^^^^^
   |
   = note: `#[warn(clippy::ptr_arg)]` on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg

Here, you're forcing the caller to have a Vec of clients. Generally, you should never take a reference to a Vec. Instead, just write &[TcpStream].

using clone on a double-reference; this will copy the reference instead of cloning the inner type

error: using `clone` on a double-reference; this will copy the reference instead of cloning the inner type
   --> src/bubble_host.rs:155:34
    |
155 |         let handle_client_func = handle_client.clone();
    |                                  ^^^^^^^^^^^^^^^^^^^^^
    |
    = note: `#[deny(clippy::clone_double_ref)]` on by default
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref

Why are you cloning the callback? Cloning a reference of a type that doesn't implement Clone, which you do, does nothing. However, you should generally use generics for this type of thing, as there could be issues if you want to use a closure—you currently require that you pass a static reference, which may not be possible. Instead, do:

fn handle_client(
...,
handle_client: impl Fn(HandleClientType) + Send + 'static,
)

There's also no need for it to be Sync, because it's only ever sent across a thread boundary, not shared.

Don't send to many clients one-after-another

for mut client in clients.iter() {
    if let Err(e) = client.write_all(&msg) {
        println!(
            "Error {} for {}",
            e,
            client.peer_addr().unwrap().to_string()
        )
    }
}

What if a client is on a 1 bit per second connection? That's still fast enough for the OS to still leave a connection open (default timeouts are generally in the hours), but they would slow stuff down for every client after them. async/await would fix that, but I understand that you're just learning and don't want to get into that just yet. So instead, the way it's typically done is to have one thread per connection, and then loop over a bunch of mpscs to send to it. You should also move to a RwLock instead of a Mutex, as that would allow you to have many threads doing non-destructive reading and writing, although you'll need to put in some work in to make sure that you're not waiting forever to write to it.

However, you could replace that entire thing with a concurrent hashmap, like DashMap. You could store an AtomicU64 that you increase every client, add that as the key and the Sender for the mpsc of the broadcast thread, then just remove the key when the client disconnects.

Final code

main.rs

mod bubble_host;
use crate::bubble_host::{BubbleServer, HandleClientType, ServerEvent};
use std::io;
use std::io::prelude::*;
use std::io::{Error, ErrorKind};
use std::net::TcpStream;
use std::thread;

///Example implementation of BubbleServer

trait TextChatServer {
    fn broad_cast_msg(&mut self, msg: &[u8]) -> Result<(), std::io::Error>;
}
impl TextChatServer for BubbleServer {
    fn broad_cast_msg(&mut self, msg: &[u8]) -> Result<(), std::io::Error> {
        let clients = self.get_clients()?;
        let clients = clients
            .lock()
            .map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;

        for mut client in clients.iter() {
            if let Err(e) = client.write_all(&msg) {
                println!(
                    "Error {} for {}",
                    e,
                    client.peer_addr().unwrap().to_string()
                )
            }
        }
        Ok(())
    }
}
fn read_stream(stream: &mut TcpStream, recv_buf: &mut [u8], read_bytes: &mut usize) -> usize {
    *read_bytes = match stream.read(recv_buf) {
        Ok(read_bytes) => read_bytes,
        Err(_) => 0,
    };
    *read_bytes
}
trait TcpHelper {
    fn send_str(&mut self, msg: &str) -> io::Result<()>;
}
impl TcpHelper for TcpStream {
    fn send_str(&mut self, msg: &str) -> io::Result<()> {
        let data = msg.as_bytes();
        self.write_all(&data)?;
        Ok(())
    }
}
//This Function will be called and ran in a new thread every time a new user connects.
fn handle_new_text_client(client_data: HandleClientType) {
    // HandleClientType provides the stream that is being handled and the server it's being called from so the user could access functions from the server
    let buffer_size = 1000;
    let (stream, server_reference) = client_data;
    println!(
        "Client connected!! ip {}",
        stream.peer_addr().unwrap().to_string()
    );
    stream
        .send_str(&stream.peer_addr().unwrap().to_string())
        .unwrap_or_else(|err| {
            println!(
                "err writing ip to stream, ip {}, err :{}",
                &stream.peer_addr().unwrap().to_string(),
                err
            )
        });
    let mut recv_buf = vec![0u8; buffer_size];
    let mut bytes_read: usize = 0;
    while read_stream(stream, &mut recv_buf, &mut bytes_read) > 0 {
        if let Err(e) = server_reference.broad_cast_msg(&recv_buf[..bytes_read]) {
            println!("Error: {}", e);
        };
    }
}

fn main() {
    env_logger::init();
    let server = BubbleServer::new(String::from("localhost:25568"));
    //Receive events from Server
    server.set_on_event(|event: ServerEvent| match event {
        ServerEvent::Disconnection(socket_addr) => println!("Disconnection from {}", socket_addr),
        _ => (),
    });
    if let Err(e) = server.start(&handle_new_text_client) {
        println!("Error starting server: {}", e);
    };
    //Blocks this thread here as server.start runs in another thread
    thread::park();
}

bubble_host.rs

use std::io;
use std::io::{Error, ErrorKind};
use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
use std::sync::mpsc::{channel, Sender};
use std::sync::*;
use std::thread;
use std::vec::Vec;
use log::debug;

/// Events for debugging and getting information from the server.
pub enum ServerEvent {
    None,
    Disconnection(SocketAddr),
}
type ErrorSender = Option<Sender<ServerEvent>>;

/// Clients List Thread Safe
pub type ClientsListTS = Arc<Mutex<Vec<TcpStream>>>;

/// Data that is sent to user provided FnMut `set in fn start` when a client connects to a
/// started(fn start) server
pub type HandleClientType<'a> = (&'a mut TcpStream, &'a mut BubbleServer);

#[derive(Clone)]
pub struct BubbleServer {
    ip: String,
    error_sender: ErrorSender,
    clients: ClientsListTS,
}

impl BubbleServer {
    pub fn new(ip: String) -> Self {
        BubbleServer {
            ip,
            clients: Default::default(),
            error_sender: None,
        }
    }

    fn get_sock_index(
        clients: &[TcpStream],
        socket_addr: &std::net::SocketAddr,
    ) -> io::Result<usize> {
        clients
            .iter()
            .position(|x| &x.peer_addr().unwrap() == socket_addr)
            .ok_or_else(|| {
                Error::new(
                    ErrorKind::InvalidInput,
                    format!("Could not find socket address: '{}' ", socket_addr),
                )
            })
    }
    /// used to retrieve socket from clients by ip address
    /// # Example:
    /// ```
    /// let server = BubbleServer::new(String::from("localhost:25568"));
    /// let addr_to_find: SocketAddr = "127.0.0.1:25565"
    ///     .parse()
    ///     .expect("Could not parse ip address!");
    /// let socket = server.get_sock(&addr_to_find);
    /// assert!(socket.is_none());
    /// ```
    #[allow(dead_code)]
    pub fn get_sock(&self, socket_addr: &SocketAddr) -> Option<TcpStream> {
        let clients = self.get_clients().ok()?;
        let clients = clients.lock().ok()?;
        if let Ok(index) = BubbleServer::get_sock_index(&clients, socket_addr) {
            Some(clients[index].try_clone().unwrap())
        } else {
            None
        }
    }

    /// Shutdown the socket, then send a disconnection event to the `error_sender`.
    fn handle_socket_disconnection(&self, socket: &TcpStream) -> io::Result<()> {
        self.remove_socket(&socket)?;
        let sock_addr = socket.peer_addr().unwrap();

        socket
            .shutdown(Shutdown::Both)
            .expect("Could not shutdown stream..");
        let event = ServerEvent::Disconnection(sock_addr);
        let sender = self.error_sender.as_ref();
        if let Some(sender) = sender {
            sender
                .send(event)
                .unwrap_or_else(|e| println!("error sending to error_sender {}", e));
        }
        debug!("Removed client {}", sock_addr);
        Ok(())
    }

    /// `get_clients` Returns clients list reference using [`Arc::clone`].
    pub fn get_clients(&self) -> io::Result<ClientsListTS> {
        Ok(Arc::clone(&self.clients))
    }
    #[allow(dead_code)]
    pub fn set_on_event<F: Fn(ServerEvent) + Send + Sync + 'static>(&self, callback: F) {
        let (err_send, err_rec) = channel::<ServerEvent>();
        self.error_sender.as_ref().get_or_insert(&err_send);
        let call = Arc::new(callback);
        std::thread::spawn(move || loop {
            let event = err_rec.recv().unwrap_or(ServerEvent::None);
            call(event);
        });
    }
    /// Removes given TcpStream from server's `clients`
    ///
    /// * Locks `clients` vec ([`Vec`]`<`[`TcpStream`]`>`)
    /// * Removes `socket` from clients list.
    fn remove_socket(&self, socket: &TcpStream) -> io::Result<()> {
        //Is this the least verbose way to dereference this?
        let clients = self.get_clients()?;
        let mut clients = clients
            .lock()
            .map_err(|err| io::Error::new(ErrorKind::Other, err.to_string()))?;

        let socket_addr = socket.peer_addr()?;

        let index = BubbleServer::get_sock_index(&clients, &socket_addr)?;
        clients.remove(index);
        Ok(())
    }
    /// Runs the user's defined function in a new thread passing in the newly connected socket.
    fn handle_client(
        &self,
        socket: &mut TcpStream,
        handle_client: impl Fn(HandleClientType) + Send + 'static,
    ) -> io::Result<()> {
        let mut socket = socket.try_clone()?;
        let mut _self = self.clone();
        thread::spawn(move || {
            handle_client((&mut socket, &mut _self));

            _self
                .handle_socket_disconnection(&socket)
                .unwrap_or_else(|e| {
                    println!("Error in handling socket disconnection, Err: '{}' ", e);
                });
        });
        Ok(())
    }
    ///Locks `clients` ([`Vec`]`<`[`TcpStream`]`>`) and adds `socket` to Vec
    fn add_client(&self, socket: TcpStream) {
        self.clients.lock().unwrap().push(socket);
    }

    /// Continously accepts incoming connections
    /// * Is non blocking.
    /// * Locks clients list when user connects
    pub fn start(&self, handle_client_cb: impl Fn(HandleClientType) + Send + Clone + 'static) -> io::Result<()> {
        let ip = &self.ip;
        debug!("server with  ip of {} has started...", ip);
        let socket_acceptor = TcpListener::bind(ip).expect("Failed to initialize server...");

        //I'm not a fan of cloning the server struct, but I didn't want the user to have the potential to deadlock the server
        //without explicitly locking mutex's in their own code.
        //for example it should be obvious that this will deadlock when a user connects
        // ```
        // {
        //     let server = BubbleServer::new(String::from("localhost:25568"));
        //     let clients = server.get_clients().expect("could not get clients!");
        //     let clients = clients.lock().unwrap();
        //     server.start(&|data: HandleClientType| {
        //         println!("new connection! dropping now!");
        //     });

        //     thread::park();
        // }
        // ```
        let self_ = self.clone();

        // Accept new incoming connections
        thread::spawn(move || loop {
            println!("waiting for next client..");
            if let Ok((mut socket, _)) = socket_acceptor.accept() {
                //Add Client to the clients vector
                self_.add_client(
                    socket
                        .try_clone()
                        .expect("Could not clone socket before handling socket.."),
                );
                //Run user's implementation on how to deal with new client
                self_
                    .handle_client(&mut socket, handle_client_cb.clone())
                    .unwrap_or_else(|e| {
                        println!("Error in handle_client : {}", e);
                    });
            }
            // Could provide implementation for an erroneous accept, and send that to the error_sender.
            // but if many clients were to make erroneous connections this might just clutter the error_sender's receive.
        });
        Ok(())
    }
}
```
\$\endgroup\$
1
  • \$\begingroup\$ Great feedback that is very useful for others to learn! \$\endgroup\$
    – Ben Usman
    Commented Oct 5, 2021 at 19:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.