6chan is an anonymous messaging board (like a group chat / forum) with file sharing.
To ensure users' privacy, all user and message data is processed in RAM, so it's all destroyed once server is shut down.
Supports sockets as well as named pipes.
For pipe-enabled version, check with-pipe
branch.
The master
branch includes socket version only.
Run server
:
server.exe
server.exe [port]
server.exe [host] [port]
server.exe [pipe]
(for pipe version)
Default is 127.0.0.1:5000
(for sockets), \\.\pipe\6chan
(for pipes)
Server writes logs to stderr, which can be piped to file: server.exe 2> server.log
Run client
:
client.exe
client.exe [port]
client.exe [host] [port]
client.exe [pipe]
(for pipe version)
Client connects to host:port or pipe and establishes session. On connect, message history syncs automatically.
/file
- upload file/dl <id>
- download file or message by#id
/sync <id>
- sync manually, starting after#id
(unused, unless network errors occur)/q
- quit
-D DEBUG
(./CMakeLists.txt
) to build debug version: extended logging, slower polling rates-D USE_COLOR
(./client/CMakeLists.txt
) to colorize console text (recommended)-D USE_PIPES
(./CMakeLists.txt
) to build pipe version. Blocking mode (PIPE_WAIT
) is used.
List of server controllers:
-
startServer()
- Initialize socket(), bind(), listen() (for socket version)
- Initialize global Client List and Message History
- Call
startAllControllers()
- Clean up global lists
-
startAllControllers()
- Initialize Critical Section for Message History
- Create thread for
clientMgmtController()
- Wait for stdin
- Close socket and set cv_stop flag
- Wait for clientMgmtController()
-
clientMgmtController()
- Call accept() in loop (for socket version), CreateFile() and ConnectNamedPipe() (for pipe version)
- For each client socket / pipe, create
messageController()
thread - If socket / pipe is closed, wait for all controller threads
-
messageController()
- Receive client data in loop
- Call
parseMessageFromClient()
to form a Message from raw buffer - Process message based on message type:
- Sync: for each new message (if any) call
sendMessageToClient()
, separate sent messages by\0
, end with\0\0
- Download: find file in Message History, call
sendFileToClient()
- File, Message: add record to Message History
- Sync: for each new message (if any) call
List of client routines and services:
-
runClient()
- Initialize socket(), connect() (for socket version), _Create
- Call
startAllServices()
-
startAllServices()
- Initialize Critical Sections for send() and recv()
- Create event for client stop
- Create threads for services:
syncService()
sendService()
- Wait for event
- Close socket
- Wait for remaining threads
-
syncService()
- Send
/sync <last_msg_id>
command in loop (uses lock for send()) - Receive until
\0
, print message (uses lock for recv()) - Update
last_msg_id
based on last incoming message id - Stop receiving on
\0\0
(message is empty) - Sleep for polling delay
- Send
-
sendService()
- Process user input in loop
- Parse commands:
/file
- callclientUploadFile()
, uses lock for send()/dl <id>
- callclientDownloadFile()
, uses locks for send() and recv()/q
- set event, set stop flag, and return
- If input is not a command, send message. uses lock for send()
Both client and server use the following recv() wrappers:
recvuntil(char delim)
: Allocate buffer and receive untildelim
character encountersrecvlen(int len)
: Allocate buffer and receive exactlylen
bytes
They work with named pipes as well, provided USE_PIPES
flag is set.
They use thread-local (for server) or shared (for client) static buffer. Buffer state persists between calls.
buf
= persistent buffer, shared by recvuntil() and recvlen()
end
= current index of last byte in buffer
size
= current allocated buffer size
Here is pseudocode for recvuntil(). Function recvlen() has similar algorithm.
if buf == NULL:
allocate buf
size = BASE_LEN
while True:
if delim in buf[0:end]:
pos = index of delim
allocate 'return buffer', copy first `pos` bytes
pop first `pos` bytes from buffer, shift remaining data
shrink buffer (if needed), decrease size
ptr = 'return buffer'
return len
else:
n = recv( &buf[end] <- (size-end) bytes )
end = end + n
if end > MAX_SIZE:
clear buffer, return 1
if end >= size:
extend buffer, increase size
0 end end+n size
buf ===============-----------|-----------
recv -> ============
0 end size
buf ===========================-----------
recv -> ==================
0 end size
buf ======================================--------------------------
recv -> =======
0 end pos size
buf =============================================------|------------
recv -> ======d=======
0 pos end size
buf ===================================================d=======-----
v v v v v v v v v v v
ret ===================================================
0 end size
buf ========--------------------------------------------------------
0 end size
buf ========-------------------------
This lab uses recvuntil('\0')
for messages and commands, which means data is received dynamically. Though it works fine, it's not the best approach, as we don't know message length beforehand, and message type is parsed from /
-like commands.
Better implement your own TV / TLV protocol for messages (use some tags from Message
struct) and receive values with recvlen()
.
And unicode... Technically should work but needs a bit of polishing. I'm too lazy for that ฅ ^•ﻌ•^ ฅ ° 。
˚∧_∧ + —̳͟͞͞💗
( •‿•)つ —̳͟͞͞ 💗 —̳͟͞͞💗 +
(つ < —̳͟͞͞💗
| _つ + —̳͟͞͞💗 —̳͟͞͞💗 ˚
`し´