From cab382db088d9f240253466a1c5a26c62f3967c8 Mon Sep 17 00:00:00 2001 From: justanothercatgirl Date: Sat, 8 Feb 2025 22:13:52 +0300 Subject: Implemented multiplexing in main thread (TCP loop). Removed CMake files. TODO: Implement multiplexing in worker threads (UDP loops), implement channel_pool interface. --- doc/channel_multiplexing | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 doc/channel_multiplexing (limited to 'doc/channel_multiplexing') diff --git a/doc/channel_multiplexing b/doc/channel_multiplexing new file mode 100644 index 0000000..a1ba681 --- /dev/null +++ b/doc/channel_multiplexing @@ -0,0 +1,84 @@ +This document is mostly for the developer to settle down ideas that are sitting +in his mind. + +Server structure: +Main thread: accepting TCP socket and reading-writing TCP sockets created by +an accepting one. TODO: rewrite so that the entire system is using poll. + +Other `get_nprocs() - 1` threads (if `get_nprocs() == 1`, then only 1 +additional thread) are channel pools. + +Additionally, main thread maintains a list of all channels and threads +associated with them. When new channel is requested, is is created on a thread +that is maintaining the least amount of channels at the time. + +Each channel pool consists of an anonymous UNIX domain socket (which is used +for recieving system messages from main thread) and some amount of INET domain +sockets. All of them are switched on using `poll` system call. + +Each channel in the channel pool has an associated list of peers with it. When +data is recieved on a channel, it is forwarded to every peer (except the +sender). + +The basic structure of the loop should look something like this: + +``` +fds = {timerfd, master socket, udp1, udp2, ...}; +while (poll(fds, nfds, -1) > 0) { + if (socket is master and has data) process_system_commands(); + if (timerfd is ready) check_keepalives(all sockets); + for (every socket that has data) + handle_peers(socket); +} + +void process_system_commands() { + command = read(master); + switch (parse_command()) { + case add_user: addto(channel, user); break; + case add_channel: array_append(channels, new_channel()); break; + case remove_user: rmfrom(channel, user); break; + case remove_channel: array_pop(channel); break; + } +} + +void handle_peers(int socket) { + data = read(socket); + for (every peer of socket) + sendto(socket, data, peer, O_NONBLOCK); // MSG_DONTWAIT, maybe? +} + +void check_keepalives(int socket) { + for (every peer of socket) { + if (keepalive of peer too old) rmfrom(channel, user); + } +} +``` + +Data structures: +inside main thread: + struct channel{u64 id, int port}; + struct channel_pool { + u16 num, + int master_pipe, + u64 thread_id, + array chs + }; + // modern linux kernel has limit on maximim amount of cores: + // 8192. this would break 65535 port limit very fast anyways. + array threads; +inside pool: + struct ch_user { u64 id, u32 ip, u16 port, u64 last_keepalive }; + struct channel { + u64 id, + u64 owner, + int udo, + hash_set* users + }; + array channels; + int master = pipe + +Communication between threads will be done through pipes. Yes, this will be +inefficient in a sense that there will be 2 file descriptors opened for each +thread, but what are other options? Yes, none. If there are, mail this to me +please + -- cgit v1.2.3-70-g09d2