Compare commits

..

No commits in common. "master" and "mango" have entirely different histories.

14 changed files with 84 additions and 628 deletions

View File

@ -1,149 +0,0 @@
# Hermes project report
## Douglas Richardson
### April 28, 2017
# Overview
Hermes is a multi-user chat program that allows users to setup a server, connect to it
and communicate with all other members of the server.
Hermes uses TCP pipes to tread input and output like a port. Essentially, each client sends
information to the server and depending on the input, the server decides what to do with it
and usually sends output back to all the other users.
# Libraries Used
The code uses two non-default libraries:
```
(require racket/gui/base)
(require math/base)
```
* The ```racket/gui/base``` library is the primary library for the GUI.
* the ```math/base``` is used for random number generation.
# 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. Initializing the gui
This line of code allows us to wrap the gui into an object.
```
(define (make-gui)
...
(cond ((eq? command 'show) (lambda () (send main-frame show #t)))
((eq? command 'get-color) get-my-color)
((eq? command 'set-color) set-color)
((eq? command 'prompt-color) prompt-color)
((eq? command 'prompt-username) prompt-username)
((eq? command 'prompt-hostname) prompt-hostname)
((eq? command 'send) send-message) ;; call to show a message in a gui
((eq? command 'set-name) (lambda (newname) (if (string? newname)
(set! name newname)
(print "Thats not good"))))
; ((eq? command 'recieve-message) user-message)
; ((eq? command 'get-list) listy)
; ((eq? command 'set-list) update)
;;Something up with that
; else should assume a message and output to screen we do not want it
; to fail
((eq? command 'get-message) get-message)
(else (error "Invalid Request" command))
))
;;dispatch goes below that
dispatch)
```
This allows us to make our code simpler and lets us treat the gui like an object in it's self.
Giving the gui commands to change it's self rather than having to remember all the commands it has.
## 2. Working with lists
This code is code that allows us to append a new message onto the end of the list of messages using recursion
```
(define (appendlist listoflist add-to-end)
(if (null? listoflist)
(cons add-to-end '())
(cons (car listoflist) (appendlist (cdr listoflist) add-to-end))))
```
Normally there is a function to just append onto the end of a list, however the problem is that if we attempt to append
a list of elements onto the end of a list, it just appends the elements onto the end of the list. For example if I had
a list of the following '(("Doug" "Hello World!" "Purple"))
and wanted to append the list '("Gordon" "No one else is here Doug." "Black") The list I want back would be
'(("Doug" "Hello World!" "Purple")("Gordon" "No one else is here Doug." "Black")) but if I use the default
list append I get'(("Doug" "Hello World!" "Purple")"Gordon" "No one else is here Doug." "Black")
which is no good for the gui.
This follows on our idea of working with lists and using recursion to walk down a list.
## 3. Re-drawing messages
The following procedure is used to re-draw messages onto the canvas after a screen move or resize.
```
(define (update-helper given-list)
(if (null? given-list)
'()
(if (null? (car given-list))
'()
(begin (user-message
(get-username-from-list (car given-list))
(get-message-from-list (car given-list))
(get-color-from-list (car given-list)))
(update-helper (cdr given-list))))))
```
While it doesn't actually use the map function, this is a map as for every element of a list (each element is a list of three strings)
it runs a procedure (or in this case a set of procedures) in the order of the list.
## 4. Parsing Messages
This line of code is used to parse a single string message into a three string message
```
(define (user-message-parse string-i start)
(define (helper str index)
(if (eq? (string-ref str (+ start index)) #\~) ; regexes would allow us
; to avoid this #\~
(substring str start (+ start index))
(helper str (+ index 1))))
(helper string-i 0))
```
This was used to parse a string into smaller strings. In hermes we can only send one string to each client at one time, therefore
the three elements that the gui uses to print messages need to be compressed together. We append a ~ inbetween each of these so we can
parse them out at the client end.
While we don't run any commands off it (saved that part for the commands we do interpret from strings)
it is similar to the symbolic differentaitor.
## 5. Color setting
Here we have an example of when we use a symbolic differentiator in the gui to determine when a user wants to run a command
rather than input text.
```
(define (button-do-stuff b e);b and e do nothing :/
(if (color-change-request? (send input get-value))
(set! my-color (get-color-from-input (send input get-value)))
...
(define (color-change-request? given-string)
(if (> (string-length given-string) 7)
(if (equal? (substring given-string 0 6) "/color")
#t
#f)
#f))
```
The procedure button-do-stuff is run every time the user presses the return key or presses the send button on the gui
and what it will do is check to see if the user typed in "/color", and if they did it sets the internal color to be
what the user said after that. This is part of our symbolic differentiator that allows the user to use commands
rather than the typical use of the input (which is just to send a message to other clients)

View File

@ -25,8 +25,6 @@
[label "Hermes"]
[width 500]
[height 700]
[min-width 500]
[min-height 700]
))
;;Editing canvas
(define (do-stuff-paint paint-canvas paint-dc)
@ -49,8 +47,6 @@
[parent main-frame]
[paint-callback do-stuff-paint]
[style '(hscroll vscroll)]
[min-width 450]
[min-height 690]
))
; "send" is rackets way of doing object-oriented programming. It calls an
@ -62,13 +58,6 @@
(if (equal? 'text-field-enter (send other-thing get-event-type))
(button-do-stuff 'irrelevant 'not-used)
'()))
; Create a horizontal panel to house the send button and text-field
(define hpanel (new horizontal-panel%
[parent main-frame]
[min-width 450]
[min-height 10]
[alignment (list 'left 'center)]))
; creates the editing area as part of the parent "main-frame" define above.
; initially labelled "Username:"
@ -76,7 +65,7 @@
; the window dimensions to fit the new label (the actual username)
; TODO make label setable
(define input (new text-field%
[parent hpanel]
[parent main-frame]
[label "username "]
[callback text-field-callback]
))
@ -122,13 +111,10 @@
one-message)))
; creates the send button
(define send-button (new button%
[parent hpanel]
[parent main-frame]
[label "Send"]
[callback button-do-stuff]))
(send input focus) ; move focus to text-field
; get-dc retrieves the canvas' device context. From racket docs. A dc object
; is a drawing context for drawing graphics and text. It represents output
; devices in a generic way.
@ -162,7 +148,7 @@
(send dc draw-text input 0 height) ;; just print message to string
(set! listy (appendlist listy (list username input color height)))
(set! height (+ height 15)) ; 15 is space between messages
(set! height (+ height 15))
; redraw overly long text on gui
(set! min-v-size (+ min-v-size 15))
(if (> (* 20 (string-length input)) min-h-size)
@ -243,34 +229,18 @@
;; prompt user for username
;; could randomly assign a user
;; after calling get-text set it as new label of text-field
; TODO there is a pattern here could wrap all this into resusable prompt funciton
;
(define (prompt-username)
(define returned (get-text-from-user "Username set-up" "Please enter a username"
main-frame "user" (list 'disallow-invalid)
#:validate
(lambda (input)
(if (and (string? input) (<= (string-length input) 10)
(>= (string-length input) 2))
#t
#f))))
(define (get-username)
(define returned (get-text-from-user "Username set-up" "Please enter a username"
main-frame "user" (list 'disallow-invalid)
#:validate
(lambda (input)
(if (and (string? input) (<= (string-length input) 10)
(>= (string-length input) 2))
#t
#f))))
(send input set-label returned)
returned)
(define (prompt-hostname)
(define returned (get-text-from-user "Hostname set-up" "Please enter a hostname"
main-frame "67.186.191.81" (list 'disallow-invalid)
#:validate
(lambda (input)
(if (and (string? input) (<= (string-length input) 50)
(>= (string-length input) 2))
#t
#f))))
; (send input set-label returned)
returned)
;;dispatch goes below that
;; TODO get username function maybe
(define (dispatch command)
@ -280,8 +250,7 @@
((eq? command 'get-color) get-my-color)
((eq? command 'set-color) set-color)
((eq? command 'prompt-color) prompt-color)
((eq? command 'prompt-username) prompt-username)
((eq? command 'prompt-hostname) prompt-hostname)
((eq? command 'get-username) get-username)
((eq? command 'send) send-message) ;; call to show a message in a gui
((eq? command 'set-name) (lambda (newname) (if (string? newname)
(set! name newname)
@ -349,6 +318,4 @@
(substring given-string 7))
;(define thing1 (make-gui))
;(define thing2 (make-gui))
; (define hermes-gui (make-gui))
; ((hermes-gui 'show))

View File

@ -1,4 +1,5 @@
# Hermes - the code
# This is a work in progress. Kindly report all issues
## Installation
The only pre-requisite is to have a recent version of Drracket, then go ahead
@ -17,16 +18,11 @@ you are good to go.
### Clients
In the clients follow the prompts to set you up. Type in messages to send to
other clients. You may try connecting to server instance running locally
"localhost" or have an ip-address for a hermes server running elsewhere.
#### Commands
* Change color of your messages with /color color. Default is black.
* You can list users in chat with /list users.
* You can get the count of users with /list count.
* If you want to send a message to a particular user, do /whisper username message in chat.
* If you want to leave chat, type /quit.
In the clients follow the prompt to set you up. Type in messages to send to
other clients. You can list users in chat with /list users. You can get the
count of users with /list count. If you want to send a message to a particular
user, do /whisper username message in chat. If you want to leave chat, type in
/quit. As a consequence you can't use /quit alone in your messages.
### Server

View File

@ -1,6 +1,7 @@
FEATURES
need to pass color settings between users
16. plain tcp -> ssl based
***17. fix breaks for improper disconnects from clients
18. Add topics after project completion
** regexes to parse strings for different formats -related to 5
** align code better for readability
@ -14,5 +15,3 @@ additionally save user details and prompt user to use defaults or create
new ones
10. authentication for databases - to avoid dependencies this is left out
** whispers aren't currently logged - its on purpose
automated test sets for networking and client code
encryption over SSL using root certificates

View File

@ -1,8 +1,4 @@
#lang racket
; Author: Ibrahim Mkusa
; About: code that enables communication with the client. It uses GUI code
; authored by Doug-Richardson
(require "modules/general.rkt" "GUI.rkt")
(require math/base) ;; for random number generation
@ -19,8 +15,7 @@
;(sleep 0.25)
; (define host3 "localhost")
(define hostname ((hermes-gui 'prompt-hostname)))
(define host3 "localhost")
(define port-num 4321)
(define sleep-t 0.1)
@ -43,14 +38,21 @@
(define (client port-no)
(parameterize ([current-custodian main-client-cust])
;; connect to server at port 8080
;; TODO catch error here
(define-values (in out) (tcp-connect hostname port-no)) ;; define values
(define-values (in out) (tcp-connect host3 port-no)) ;; define values
;; binds to multiple values akin to unpacking tuples in python
;; TODO could store theses info in a file for retrieval later
(define username ((hermes-gui 'prompt-username)))
((hermes-gui 'prompt-color))
; store username to a file for later retrieval along with relevent
; info used for authentication with server
; TODO
; semaphore for gui object
; could display a bubble and prompt for username in GUI object
; create a gui object
; (define hermes-gui (make-gui))
; ((hermes-gui 'show))
;(displayln "What's your name?")
;(define username (read-line))
(define username ((hermes-gui 'get-username)))
;send the username to the server (username in out)
(displayln username out)
@ -86,11 +88,11 @@
; get current time
(define date-today (seconds->date (current-seconds) #t))
;TODO pad the second if its only 1 character
(define date-print (string-append (pad-date (number->string (date-hour date-today)))
(define date-print (string-append (number->string (date-hour date-today))
":"
(pad-date (number->string (date-minute date-today)))
(number->string (date-minute date-today))
":"
(pad-date (number->string (date-second date-today)))
(number->string (date-second date-today))
" | "))
;; read, quits when user types in "quit"
;; TODO read from GUI instead
@ -98,20 +100,18 @@
;(semaphore-wait hermes-gui-s)
(define input ((hermes-gui 'get-message)))
;(semaphore-post hermes-gui-s)
; TODO prompt for color as well
; /color color is appended to input to specify the color the message should
; be displayed in
; TODO /quit instead of quit
(cond ((string=? input "/quit")
(displayln (string-append date-print username " signing out. See ya!"
" /color " ((hermes-gui 'get-color))) out)
(displayln (string-append date-print username " signing out. See ya!") out)
(flush-output out)
(close-output-port error-out)
(close-output-port convs-out)
;(custodian-shutdown-all main-client-cust)
(exit)))
(displayln (string-append date-print username ": " input
" /color " ((hermes-gui 'get-color))) out)
(displayln (string-append date-print username ": " input) out)
(flush-output out))
; a wrap around to call ((hermes-gui 'send) zzz yyy) without complaints from
@ -127,22 +127,15 @@
(cond [(eof-object? evt)
(displayln-safe "Server connection closed." error-out-s error-out)
(exit)
;(custodian-shutdown-all main-client-cust)
(custodian-shutdown-all main-client-cust)
;(exit)
]
[(string? evt)
(displayln-safe evt convs-out-s convs-out)
(define evt-matched
(regexp-match #px"(.*)\\s+/color\\s+(\\w+).*"
evt))
; TODO set color to current client if the message is from him
; otherwise set it to the remote
;(semaphore-wait hermes-gui-s)
;(send-to-gui evt ((hermes-gui 'get-color)))
; extracts the message and color from received message
(send-to-gui (cadr evt-matched) (caddr evt-matched))
(send-to-gui evt ((hermes-gui 'get-color)))
;(semaphore-post hermes-gui-s)
] ; could time stamp here or to send message
[else

View File

@ -1,78 +0,0 @@
Number of users in chat: 1 /color blue
Number of users in chat: 2 /color blue
18:15:50 | Ibrahim: hello /color violet
18:15:29 | Doug: fsdfdsfads /color red
18:16:08 | Doug signing out. See ya! /color red
18:16:01 | Ibrahim signing out. See ya! /color violet
Number of users in chat: 1 /color blue
18:21:58 | Ibrahim signing out. See ya! /color blue
Number of users in chat: 1 /color blue
18:24:14 | Doug signing out. See ya! /color black
Number of users in chat: 1 /color blue
11:12:05 | Ibrahim signing out. See ya! /color black
Number of users in chat: 1 /color blue
11:12:39 | user signing out. See ya! /color green
Number of users in chat: 1 /color blue
Number of users in chat: 2 /color blue
11:16:51 | Doug: whats going on? /color blue
11:16:41 | Ibrahim: I am alright /color green
11:17:26 | Ibrahim: heheheh /color red
11:17:35 | Ibrahim signing out. See ya! /color red
11:17:50 | Doug signing out. See ya! /color blue
Number of users in chat: 1 /color blue
Number of users in chat: 2 /color blue
12:02:59 | Doug: Yo /color black
12:05:27 | Ibrahim signing out. See ya! /color green
Number of users in chat: 2 /color blue
12:07:31 | Ibrahi: hello /color blue
12:07:51 | Ibrahi: how are you, Doug? /color blue
12:03:40 | Doug: Doing well thanks /color black
12:09:09 | Doug: Hello there /color black
12:09:57 | Doug: Hello /color purple
12:09:08 | Ibrahi: What's up fellaz /color blue
12:13:57 | Ibrahi: hello /color blue
12:16:04 | Ibrahi: hello dou /color red
12:11:44 | Doug: say whatever we want /color purple
12:18:14 | Doug: now im eco friendly /color green
12:18:48 | Doug: /listusers /color green
12:16:42 | Ibrahi signing out. See ya! /color red
Number of users in chat: 1 /color blue
Number of users in chat: 2 /color blue
12:20:18 | Jill: Doesnt matter /color Blue
12:20:28 | Jill: Does this woirk /color magenta
12:21:26 | Jill: /list user /color magenta
Number of users in chat: 2 /color blue
12:22:01 | Mongol: hello /color green
Number of users in chat: 3 /color blue
12:24:20 | Bob: hello /color cyan
12:21:57 | Jill: Nothing /color raddish
12:24:32 | Jill: no way /color gold
12:24:46 | Jill: does this work? /color silver
12:24:57 | Jill: waht /color bronze
12:25:18 | Jill: maybe? /color terquoise
12:25:33 | Jill: this one cant work /color peach
12:25:48 | Jill: yeah /color violet
12:26:15 | Jill: hep /color purple
12:26:25 | Jill: what /color indego
12:26:53 | Jill: so like this /color indigo
12:22:06 | Mongol: i'm rolling /color gold
12:27:07 | Jill: orange /color orange
12:27:32 | Jill: type stuff /color orange
12:32:13 | Jill: now its red /color red
12:34:14 | Jill: What /color red
12:35:35 | Jill: something /color red
12:36:21 | Jill: anything else /color red
12:36:26 | Jill: now its blue /color blue
12:27:27 | Mongol: hello world /color gold
12:41:47 | Mongol: hello /color green
12:37:19 | Jill: anything in here /color blue
12:43:22 | Jill: i can do that /color silver
12:27:58 | Bob signing out. See ya! /color cyan
Number of users in chat: 3 /color blue
12:44:10 | Julian: hohoho /color red
12:43:36 | Jill: something /color black
12:44:50 | Jill: say something else /color pink
12:45:21 | Jill: works better /color purple
12:45:55 | Jill signing out. See ya! /color purple
12:42:05 | Mongol signing out. See ya! /color green
12:44:19 | Julian signing out. See ya! /color red

View File

@ -1,28 +0,0 @@
Starting up the listener.
Listener successfully started.
Server process started.
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Goodbye, shutting down all services
Starting up the listener.
Listener successfully started.
Server process started.
Connection closed. EOF received
Connection closed. EOF received
Goodbye, shutting down all services
Starting up the listener.
Listener successfully started.
Server process started.
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Connection closed. EOF received
Goodbye, shutting down all services

View File

@ -1,6 +1,6 @@
#lang racket
(provide displayln-safe pad-date)
(provide displayln-safe)
;; Several threads may want to print to stdout, so lets make things civil
; constant always available
(define stdout (make-semaphore 1))
@ -22,8 +22,3 @@
(displayln a-string)
(semaphore-post stdout)])))
; adds padding to dates
(define (pad-date date-element)
(if (> (string-length date-element) 1)
date-element
(string-append "0" date-element)))

View File

@ -1,15 +1,11 @@
#lang racket
; Author: Ibrahim Mkusa
; About: code that powers Hermes server
(require "modules/general.rkt") ;; common function(s)
(require math/base) ;; for random number generation
;; server messages in blue
(define welcome-message "Welcome to Hermes coms. Type your message below /color blue ")
(define successful-connection-m "Successfully connected to a client. Sending client a welcome message. /color blue ")
(define welcome-message "Welcome to Hermes coms. Type your message below")
(define successful-connection-m "Successfully connected to a client. Sending client a welcome message.")
(define sleep-t 0.1)
@ -103,7 +99,7 @@
(displayln-safe "Listener successfully started." error-out-s error-out)
;; Create a thread whose job is to simply call broadcast iteratively
(thread (lambda ()
(displayln-safe "Broadcast thread started!")
(displayln-safe "Broadcast thread started!\n")
(let loopb []
(sleep sleep-t) ;; wait 0.5 secs before beginning to broadcast
(broadcast)
@ -137,8 +133,7 @@
(displayln welcome-message out)
;; print to server log and client
(define print-no-users (string-append "Number of users in chat: "
(number->string ((c-count 'current-count)))
" /color blue"))
(number->string ((c-count 'current-count)))))
(displayln print-no-users out)
(displayln-safe print-no-users convs-out-s convs-out)
(flush-output out)
@ -157,7 +152,7 @@
(displayln-safe (string-append
"Started a thread to kill hanging "
"connecting threads"))
(sleep 7200) ; kills clients threads after a while could refresh this on new message
(sleep 1360)
(custodian-shutdown-all cust)))))
; whisper selector for the username and message
@ -191,38 +186,33 @@
(define list-count (regexp-match #px"(.*)/list\\s+count\\s*" evt-t0)) ;; is client asking for number of logged in users
(define list-users (regexp-match #px"(.*)/list\\s+users\\s*" evt-t0)) ;; user names
; do something whether it was a message, a whisper, request for number of users and so on
; TODO if user doesn't exist handle it********
(cond [whisper
(semaphore-wait connections-s)
; get output port for user
; this might be null
(define that-user-ports
(filter
(first (filter
(lambda (ports)
(if (string=? (whisper-to whisper) (get-username ports))
#t
#f))
((c-connections 'cons-list))))
((c-connections 'cons-list)))))
; try to send that user the whisper
(if (and (null? that-user-ports)
#t) ; #t is placeholder for further checks
(if (port-closed? (get-output-port that-user-ports))
(begin
(displayln "User is unavailable. /color blue" out)
(displayln "User is unavailable" 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)))))
(displayln (string-append (whisper-info whisper) (whisper-message whisper))
(get-output-port that-user-ports))
(flush-output (get-output-port that-user-ports))))
(semaphore-post connections-s)]
[list-count
;;should put a semaphore on connections
(semaphore-wait c-count-s)
(semaphore-wait connections-s)
(define no-of-users (string-append "Number of users in chat: "
(number->string ((c-count 'current-count)))
" /color blue"))
(number->string ((c-count 'current-count)))))
(displayln no-of-users out)
(flush-output out)
(semaphore-post connections-s)
@ -231,10 +221,10 @@
[list-users
(semaphore-wait connections-s)
; map over connections sending the username to the client
(displayln "Here is a list of users in chat. /color blue" out)
(displayln "Here is a list of users in chat." out)
(map
(lambda (ports)
(displayln (string-append (get-username ports) " /color blue") out))
(displayln (get-username ports) out))
((c-connections 'cons-list)))
(flush-output out)
(semaphore-post connections-s)]
@ -291,4 +281,4 @@
(semaphore-post messages-s)))
(define stop-server (serve 4321)) ;; start server then close with stop
(displayln-safe "Server process started." error-out-s error-out)
(displayln-safe "Server process started\n" error-out-s error-out)

View File

@ -1,226 +0,0 @@
# 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.
# 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```.
```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.
```
(define (whisper-info exp)
(cadr exp))
(define (whisper-to exp)
(caddr exp))
(define (whisper-message exp)
(cadddr exp))
```
```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.

View File

@ -1,8 +1,5 @@
# Hermes
![example](https://github.com/oplS17projects/Hermes/blob/master/ext/Test_Figure.png)
### Statement
Hermes is a multi-client chat program akin to IRC written in Racket. Building
Hermes was interesting as it exposed us to various design problems namely networking,
@ -16,22 +13,19 @@ definition of a message.
> Will you use recursion? How?
The server continually loops waiting for connections from clients. The clients
are always on standby to receive input.
The server continually loops waiting for connections from clients.
The GUI continually loops to handle input from the user,
as well as to keep the canvas it writes the messages on updated.
> Will you use map/filter/reduce? How?
Map was used for dealing with input area of clients, and iterating over a list
of open ports to send messages. Filter was used to find the recipient of
a whisper.
Map will be used for dealing with input area of clients, and iterating over a list
of open ports to send messages.
> Will you use object-orientation? How?
Keeping count of the number of clients required working with objects that are able to
increment and decrement the number of users. We handled a list of connection
ports, messages similarly.
increment and decrement the number of users.
We also keep the GUI in an object so the many moving parts of the
user interface are packaged in one place.
@ -40,20 +34,18 @@ user interface are packaged in one place.
The communication part of Hermes is over tcp which uses a lot of functional
approaches e.g. you start a listener which you can call tcp-accept on.
The result of tcp accept are two pairs of ports which we can then bind to some
variables. Functional approaches are exemplied in most of the code base.
variables.
> Will you use state-modification approaches? How? (If so, this should be encapsulated within objects. `set!` pretty much should only exist inside an object.)
State-modification was used e.g. keeping count of logged in users requires
state modification via set! to maintain the true user account, managing the list
of open connections and messages required state-modification.
State-modification will be used e.g. keeping count of logged in users requires
state modification via set! to maintain the true user account.
The user interface also needs a few states that it needs to keep up to date.
> Will you build an expression evaluator, like we did in the symbolic differentatior and the metacircular evaluator?
We allowed the use of a few commands through the user interface. The most notable ones
are the /whisper to send private messages to a user, /list count and /list users
to view user statistics , and the /color command to allow
We allow the use of a few commands through the user interface. The most notable ones
are the /quit command to shut down a connection and the /color command to allow
the user to change the color of their text.
### Deliverable and Demonstration
@ -61,7 +53,7 @@ There are two big deliverables for this project. Code for the server
, and the clients which not only has code for interacting with Hermes,
but also a GUI for interactivity with a user.
We are going to demonstrate Hermes by running the server code on a remote machine.
We will demonstrate Hermes by running the server code on a remote machine.
We will connect to the server via our PCs running client code. We will ssh into
the remote machine to see the server running. Since Hermes is a multichat anyone
can join in the demonstration by connecting their computers to the remote
@ -70,18 +62,23 @@ machine!
### Evaluation of Results
Evaluating Hermes was very simple. Can at least two clients hold a meaningful
Evaluating Hermes is very simple. Can at least two clients hold a meaningful
conversation remotely? If Client A speaks at 11:01 am, and client B does so at
11:01 plus a few seconds, Hermes has to convey this state correctly. Is the GUI
intuitive for current irc users? We successfully met these questions, and more.
intuitive for current irc users? When we can successfully answer this questions
satisfactorily we would have met our goals.
## Architecture Diagram
#### Completed design
#### Preliminary design
![Architecture](https://github.com/oplS17projects/Hermes/blob/master/ext/arch_diagram.png)
#### The Game plan
![Diagram](https://github.com/oplS17projects/Hermes/blob/master/ext/architecture_diagram.png)
## Schedule
The first step in our project was to setup a system to get data from one machine to another. What data exactly wasn't directly important and the other machine didn't really need to display it in a pretty manner, it just needed to relay that it has recieved the correct information.
@ -107,8 +104,8 @@ that the server provides. For the most part the program only interacts with the
through the GUI.
### Ibrahim Mkusa @iskm
I wrote the networking code i.e. code that allows communication between
clients through server. I wrote scheduling code responsible for queueing
fairly the client messages and broadcasting to the rest of connected
clients. I also implemented the logic for handling /list, /whisper commands,
dialogs for gui code and related utilities.
Will write the networking code i.e. code that allows communication between
clients through server. I will also write scheduling code responsible for queueing
fairly and orderly the client messages and broadcasting to the rest of connected
clients. If time permits, i will also be responsible for authenticating users
via a backend database.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 23 KiB