Skip to content

Commit a1008f6

Browse files
author
user
committed
6chan Release
0 parents  commit a1008f6

File tree

22 files changed

+2025
-0
lines changed

22 files changed

+2025
-0
lines changed

CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
cmake_minimum_required(VERSION 3.26)
2+
project(lab6 C)
3+
4+
set(CMAKE_C_STANDARD 23)
5+
6+
# Debug build:
7+
# - slower poll rate (5000ms vs 300ms)
8+
# - logging in stderr
9+
10+
# add_compile_definitions("-DDEBUG")
11+
12+
add_subdirectory(client)
13+
add_subdirectory(server)
14+
add_subdirectory(utils)
15+

README.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Lab 6: socket-based messaging board
2+
3+
## 6chan - Welcome back. Again.
4+
5+
6chan is an anonymous messaging board (like a group chat / forum) with file sharing. \
6+
To ensure users' privacy, all user and message data is processed in RAM, so it's all destroyed once server is shut down.
7+
8+
## Usage
9+
10+
### Program startup
11+
12+
Run `server`:
13+
* `server.exe`
14+
* `server.exe [port]`
15+
* `server.exe [host] [port]`
16+
17+
Default is `127.0.0.1:5000`. Server writes logs to _stderr_, which can be piped to file.
18+
19+
Run `client`:
20+
* `client.exe`
21+
* `client.exe [port]`
22+
* `client.exe [host] [port]`
23+
24+
Client connects to _host:port_ and establishes session. On connect, message history syncs automatically.
25+
26+
### Available commands
27+
28+
* `/file` - upload file
29+
* `/dl <id>` - download file or message by `#id`
30+
* `/sync <id>` - sync manually, starting after `#id` _(unused, unless network errors occur)_
31+
* `/q` - quit
32+
33+
## Compile definitions
34+
35+
* `-D DEBUG` (`./CMakeLists.txt`) to build debug version: extended logging, slower polling rates
36+
* `-D USE_COLOR` (`./client/CMakeLists.txt`) to colorize console text _(recommended)_
37+
38+
## Server architecture
39+
40+
List of server controllers:
41+
* `startServer()`
42+
- Initialize _socket(), bind(), listen()_
43+
- Initialize global _Client List_ and _Message History_
44+
- Call `startAllControllers()`
45+
- Clean up global lists
46+
47+
48+
* `startAllControllers()`
49+
- Initialize Critical Section for _Message History_
50+
- Create thread for `clientMgmtController()`
51+
- Wait for _stdin_
52+
- Close socket and set _cv_stop_ flag
53+
- Wait for _clientMgmtController()_
54+
55+
56+
* `clientMgmtController()`
57+
- Call _accept()_ in loop
58+
- For each client socket, create `messageController()` thread
59+
- If socket is closed, wait for all controller threads
60+
61+
62+
* `messageController()`
63+
- Receive client data in loop
64+
- Call `parseMessageFromClient()` to form a _Message_ from raw buffer
65+
- Process message based on message type:
66+
* _Sync_: send new messages (if any) to client
67+
* _Download_: find file in _Message History_, call `sendFileToClient()`
68+
* _File_, _Message_: add data to _Message History_
69+
70+
## Client architecture
71+
72+
List of client routines and services:
73+
74+
* `runClient()`
75+
- Initialize _socket(), connect()_
76+
- Call `startAllServices()`
77+
78+
79+
* `startAllServices()`
80+
- Initialize Critical Sections for _send()_ and _recv()_
81+
- Create event for client stop
82+
- Create threads for services:
83+
* `syncService()`
84+
* `recvService()`
85+
* `sendService()`
86+
- Wait for event
87+
- Close socket
88+
- Wait for remaining threads
89+
90+
91+
* `syncService()`
92+
- Send `/sync <last_msg_id>` command in loop
93+
- Sleep for polling delay
94+
- Uses lock for _send()_
95+
96+
97+
* `recvService()`
98+
- Receive until `\0` in loop
99+
- Print each message in terminal
100+
- Update `last_msg_id` based on incoming message id
101+
- Uses lock for _recv()_
102+
103+
104+
* `sendService()`
105+
- Process user input in loop
106+
- Parse commands:
107+
* `/file` - call `clientUploadFile()`, uses lock for _send()_
108+
* `/dl <id>` - call `clientDownloadFile()`, uses locks for _send()_ and _recv()_
109+
* `/q` - set event, set stop flag, and return
110+
- If input is not a command, send message. uses lock for _send()_
111+
112+
113+
## Bufferized receive: recvuntil(), recvlen()
114+
115+
Both client and server use the following _recv()_ wrappers:
116+
* `recvuntil(char delim)`: Allocate buffer and receive until `delim` character encounters
117+
* `recvlen(int len)`: Allocate buffer and receive exactly `len` bytes
118+
119+
Uses thread-local (for server) or shared (for client) static buffer.
120+
Buffer state persists between calls.
121+
122+
`buf` = persistent buffer, shared by _recvuntil()_ and _recvlen()_\
123+
`end` = current index of last byte in buffer \
124+
`size` = current allocated buffer size
125+
126+
### Algorithm
127+
Here is pseudocode for recvuntil(). Function recvlen() has similar algorithm.
128+
```
129+
if buf == NULL:
130+
allocate buf
131+
size = BASE_LEN
132+
133+
while True:
134+
if end >= len: (i.e. buffer has enough data)
135+
allocate 'return buffer', copy first `len` bytes
136+
pop first `len` bytes from buffer, shift remaining data
137+
shrink buffer, decrease size
138+
ptr = 'return buffer'
139+
return len
140+
else:
141+
n = recv( &buf[end] <- (size-end) bytes )
142+
end = end + n
143+
if end > MAX_SIZE:
144+
clear buffer, return 1
145+
if end >= size:
146+
extend buffer, increase size
147+
```
148+
149+
### Illustration:
150+
151+
```
152+
0 end end+n size
153+
buf ===============-----------|-----------
154+
recv -> ============
155+
```
156+
######
157+
```
158+
0 end size
159+
buf ===========================-----------
160+
recv -> ==================
161+
```
162+
######
163+
```
164+
0 end size
165+
buf ======================================--------------------------
166+
recv -> =======
167+
```
168+
######
169+
```
170+
0 end pos size
171+
buf =============================================------|------------
172+
recv -> ======d=======
173+
```
174+
######
175+
```
176+
0 pos end size
177+
buf ===================================================d=======-----
178+
v v v v v v v v v v v
179+
ret ===================================================
180+
```
181+
######
182+
```
183+
0 end size
184+
buf ========--------------------------------------------------------
185+
```
186+
######
187+
```
188+
0 end size
189+
buf ========-------------------------
190+
```
191+
192+
## Improvements
193+
194+
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 `/commands`.
195+
196+
Better **implement your own _TV / TLV_ protocol** for messages (use tags from `Message` struct) and receive values with `recvlen()`.

client/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
add_compile_definitions("-DUSE_COLOR")
2+
3+
add_executable(client main.c src/client.c src/fileshare.c ../utils/src/recvbuf.c)
4+
5+
target_link_libraries(client ws2_32 pthread -static)

client/include/client.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef LAB6_CLIENT_H
2+
#define LAB6_CLIENT_H
3+
4+
#include <winsock2.h>
5+
#include <ws2tcpip.h>
6+
7+
WINBOOL runClient(const char *ip, const char *port);
8+
void closeClient(ADDRINFOA *fullcli, SOCKET sock);
9+
10+
void startAllServices(ADDRINFOA *fullcli, SOCKET sock);
11+
12+
void syncService(SOCKET sock);
13+
void sendService(SOCKET sock);
14+
void recvService(SOCKET sock);
15+
16+
#endif //LAB6_CLIENT_H

client/include/color.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#ifndef LAB6_COLOR_H
2+
#define LAB6_COLOR_H
3+
#ifdef USE_COLOR
4+
5+
#include <windows.h>
6+
7+
#define COLORS_ARRAY \
8+
FOREGROUND_GREEN, \
9+
FOREGROUND_GREEN | FOREGROUND_BLUE, \
10+
FOREGROUND_RED | FOREGROUND_BLUE, \
11+
FOREGROUND_RED, \
12+
FOREGROUND_RED | FOREGROUND_GREEN, \
13+
FOREGROUND_BLUE
14+
15+
#define NUM_COLORS 6
16+
17+
#define DEFAULT_COLOR 0
18+
19+
HANDLE hConsole;
20+
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
21+
WORD saved_attr;
22+
23+
void setColor(int seed);
24+
25+
#endif //USE_COLOR
26+
#endif //LAB6_COLOR_H

client/include/fileshare.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#ifndef LAB6_FILESHARE_H
2+
#define LAB6_FILESHARE_H
3+
4+
#include <winsock2.h>
5+
6+
void clientDownloadFile(SOCKET sock, DWORD file_id, CRITICAL_SECTION *cs_send, CRITICAL_SECTION *cs_recv);
7+
void clientUploadFile(SOCKET sock, CRITICAL_SECTION *cs_send);
8+
9+
WINBOOL clientSelectOpenPath(char* path_buf);
10+
WINBOOL clientSelectSavePath(char* path_buf);
11+
12+
void printLastError();
13+
void printLastWSAError();
14+
15+
#endif //LAB6_FILESHARE_H

client/main.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include "include/client.h"
2+
3+
#define DEFAULT_HOST "127.0.0.1"
4+
#define DEFAULT_PORT "5000"
5+
6+
int main(int argc, char** argv) {
7+
/**
8+
* @usage
9+
* server.exe
10+
* server.exe [port]
11+
* server.exe [host] [port]
12+
*
13+
* default is 127.0.0.1:5000
14+
*/
15+
char *host, *port;
16+
if (argc < 2) {
17+
host = DEFAULT_HOST;
18+
port = DEFAULT_PORT;
19+
}
20+
else if (argc == 2) {
21+
host = DEFAULT_HOST;
22+
port = argv[1];
23+
}
24+
else {
25+
host = argv[1];
26+
port = argv[2];
27+
}
28+
return runClient(host, port);
29+
}

0 commit comments

Comments
 (0)