|
| 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()`. |
0 commit comments