aboutsummaryrefslogtreecommitdiff
path: root/IBRAHIM_MKUSA.md
blob: 48a7ed163008d9416da8847c343ca65d5e9bddb8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# Hermes - A chat server and client written in Racket

## Ibrahim Mkusa
### April 30, 2017

# Overview
Hermes is a chat server and client written in Racket. One can run the Hermes
server on any machine that is internet accessible. The Hermes clients then
connect to the server from anywhere on the internet. It's inspired by chat
systems and clients like irc.

The goal in building Hermes was to expose myself to several concepts integral to
systems like networking, synchronization, and multitasking.


**Authorship note:** All of the code described here was written by myself.

# Libraries Used
Most libraries and utilities used are part of base Drracket installation and
therefore do not need to be imported.

The date and time modules were used for various time related queries.
The tcp module was used for communication via Transmission Control Protocol.
Concurrency and synchronization modules that provide threads, and semaphores
were also used.

Below are libraries that were not part of base system:

```
(require racket/gui/base)
(require math/base)
```

* The ```racket/gui/base``` library used to build graphical user interface.
* The ```math/base``` library was used for testing purposes. It was used to
generated random numbers.

# Key Code Excerpts

Here is a discussion of the most essential procedures, including a description of how they embody ideas from 
UMass Lowell's COMP.3010 Organization of Programming languages course.

Five examples are shown and they are individually numbered. 

## 1. Tracking client connections using an object and closures.

The following code defines and creates a global object, ```make-connections```
that abstracts client connections. It also creates a semaphore to control access
to ```make-connections``` object.

```
(define (make-connections connections)
  (define (null-cons?)
    (null? connections))
   (define (add username in out)
    (set! connections (append connections (list (list username in out))))
    connections)
   (define (cons-list)
     connections)
   (define (remove-ports in out)
     (set! connections
       (filter 
         (lambda (ports)
           (if (and (eq? in (get-input-port ports))
                    (eq? out (get-output-port ports)))
             #f
             #t))
         connections)))
   (define (dispatch m)
     (cond [(eq? m 'null-cons) null-cons?]
           [(eq? m 'cons-list) cons-list]
           [(eq? m 'remove-ports) remove-ports]
           [(eq? m 'add) add]))
   dispatch)

(define c-connections (make-connections '()))

(define connections-s (make-semaphore 1)) ;; control access to connections
 ```
 When the tcp-listener accepts a connection from a client, the associated input
 output ports along with username  are added as an entry in ```make-connections``` via ```add``` function.
 External functions can operate on the connections by securing the semaphore,
 and then calling ```cons-list``` to expose the underlying list of connections.
 ```remove-ports``` method is also available to remove input output ports from
 managed connections.


 
 
## 2. Tracking received messages via objects and closures.

The code below manages broadcast messages from one client to the rest. It wraps
a list of strings inside an object that has functions similar to ```make-connections``` for
exposing and manipulating the list from external functions. The code creates
```make-messages``` global object and a semaphore to control access to it from
various threads of execution.

```
(define (make-messages messages)
  (define (add message)
    (set! messages (append messages (list message)))
    messages)
  (define (mes-list)
    messages)
  (define (remove-top)
    (set! messages (rest messages))
    messages)
  (define (dispatch m)
    (cond [(eq? m 'add) add]
          [(eq? m 'mes-list) mes-list]
          [(eq? m 'remove-top) remove-top]))
  dispatch)

(define c-messages (make-messages '()))

(define messages-s (make-semaphore 1))  ;; control access to messages
```

## 3. Using map to broadcast messages from client to clients

The ```broadcast``` function is called repeatedly in a loop to extract a message
from ```make-messages``` object, and send it to every other client. It uses the
```make-connections``` objects to extract output port of a client. The ```map```
routine is called on every client in the connections object to send it
a message.

```
(define broadcast
  (lambda ()
    (semaphore-wait messages-s)
    (cond [(not (null? ((c-messages 'mes-list))))
        (map
            (lambda (ports)
              (if (not (port-closed? (get-output-port ports)))
                (begin 
                    (displayln (first ((c-messages 'mes-list))) (get-output-port ports))
                    (flush-output (get-output-port ports)))
                (displayln-safe "Failed to broadcast. Port not open." error-out-s error-out)))
            ((c-connections 'cons-list)))
        (displayln-safe (first ((c-messages 'mes-list))) convs-out-s convs-out)
        ;; remove top message from "queue" after broadcasting
        ((c-messages 'remove-top))
        ; debugging displayln below
        ; (displayln "Message broadcasted")
        ]) ; end of cond
    (semaphore-post messages-s)))
```
After the message is send, the message is removed from the "queue" via the
```remove-top```.

The code snippet below creates a thread that iteratively calls ```broadcast```
every interval, where interval(in secs) is defined by ```sleep-t```.

** note ** : ```sleep``` is very important for making Hermes behave gracefully
in a system. Without it, it would be called at the rate derived from cpu clock
rate. This raises cpu temperatures substantially, and make cause a pre-mature
system shutdown.

```
(thread (lambda ()
              (displayln-safe "Broadcast thread started!")
              (let loopb []
                (sleep sleep-t)  ;; wait 0.2 ~ 0.5 secs before beginning to broadcast
                (broadcast)
                (loopb))))
```

## 4. Filtering a List of connections to find recipient of a whisper

I implemented a whisper functionality, where a user can whisper to any user in
the chat room. The whisper message is only sent to specified user. To implement
this i used ```filter``` over the connections, where the predicate tested whether the
current list item matched that of a specific user.

'''
(define whisper (regexp-match #px"(.*)/whisper\\s+(\\w+)\\s+(.*)" evt-t0))

[whisper
                  (semaphore-wait connections-s)
                  ; get output port for user
                  ; this might be null
                  (define that-user-ports
                    (filter
                     (lambda (ports)
                       (if (string=? (whisper-to whisper) (get-username ports))
                           #t
                           #f))
                     ((c-connections 'cons-list))))
                  ; try to send that user the whisper
                  (if (and (null? that-user-ports)
                           #t) ; #t is placeholder for further checks
                      (begin
                        (displayln "User is unavailable. /color blue" out)
                        (flush-output out))
                      (begin
                        (displayln (string-append "(whisper) "
                                    (whisper-info whisper) (whisper-message whisper))
                                   (get-output-port (car that-user-ports)))
                        (flush-output (get-output-port (car that-user-ports)))))
                  (semaphore-post connections-s)]
'''

The snippet above is part of cond statement that tests contents of input from
clients to determine what the client is trying wants/trying to do. The top-line
is using regexes to determine whether the received message is a whisper or not.



## 5. Selectors for dealing with content of a whisper from clients

Below are are three selectors that help abstract the contents of a whisper
message.



```
; whisper selector for the username and message
(define (whisper-info exp)
  (cadr exp))

(define (whisper-to exp)
  (caddr exp))

(define (whisper-message exp)
  (cadddr exp))
(define (list-all-folders folder-id)
  (let ((this-level (list-folders folder-id)))
    (begin
      (display (length this-level)) (display "... ")
      (append this-level
              (flatten (map list-all-folders (map get-id this-level)))))))
```

```whisper-info``` retrieves the date-time and username info.
```whisper-to``` retrieves the username of the intented recipient of a whisper.
```whisper-message``` retrieves the actual whisper.