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
|
#lang racket
(require math/base) ;; for random number generation
;; globals
;; must control access via semaphore as listener thread or broadcast thread
;; might need to access it
(define connections '()) ;; maintains a list of open ports
;; ((in1, out1), (in2, out2), (in3, out3), (in4, out4) ...)
;; lets keep thread descriptor values
;
(define fair (make-semaphore 1)) ;; managing connections above
(define can-i-broadcast (make-semaphore 1))
;; alternative one keep running list of input and output ports directly
;; broadcasts a message to all connected clients
(define broadcast-message
(lambda (message connections)
(map send_message connections)
'ok))
; port pair -> '(input-port output-port)
(define (get-input-port port-pair)
(car port-pair))
(define (get-output-port port-pair)
(cadr port-pair))
;; gets pair of input and output port of a client and sends a message
(define send-message
(lambda (client_ports)
(displayln message (get-output-port (client-ports)))
(flush-output (get-output-port (client-ports)))
'ok))
;;
;; This is a relay server making two clients communicate
;; Both `server' and `accept-and-handle' change
;; to use a custodian.
;; To start server
;; (define stop (serve 8080))
;; (stop) to close the server
(define (serve port-no)
(define main-cust (make-custodian))
(parameterize ([current-custodian main-cust])
(define listener (tcp-listen port-no 5 #t))
(define (loop)
(accept-and-handle listener)
(loop))
(thread loop))
(lambda ()
(displayln "\nGoodbye, shutting down all services\n")
(custodian-shutdown-all main-cust)))
(define (accept-and-handle listener)
(define cust (make-custodian))
(parameterize ([current-custodian cust])
(define-values (in out) (tcp-accept listener))
(semaphore-wait fair)
;; keep track of open ports
(append connections (list (list in out)))
(semaphore-wait fiar)
; thread will communicate to all clients at once in a broadcast
; manner
(thread (lambda ()
(handle in out) ;; this handles connection with that specific client
(close-input-port in)
(close-output-port out)))
)
;; Watcher thread:
;; kills current thread for waiting too long for connection from
;; clients
(thread (lambda ()
(sleep 120)
(custodian-shutdown-all cust))))
; (define (handle connections)
; ())
;; each thread needs 2 new threads
(define (handle in out)
; define function to deal with in
(define (something-to-say in)
(sync/timeout 4 (read-line-evt in 'linefeed)))
; define function to deal with out
; thread them each
; (server-loop in out)
(sleep 5) ;; wait 5 seconds to guarantee client has already send message
(define echo (read-line in)) ;; bind message to echo
(displayln (string-append echo "\n"))
; echo back the message, appending echo
; could regex match the input to extract the name
(writeln "Admin: Hello there" out) ;; append "echo " to echo and send back
(flush-output out)
)
;; This is a single server communicating directly to the client
;;;;;;;;;;;;;;;;;;;;;;Server Client communication;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (serve in-port out-port)
(let loop []
(define evt (sync/timeout 2
(read-line-evt in-port 'any)
(thread-receive-evt)))
(cond
[(not evt)
(displayln "Timed out, exiting")
(tcp-abandon-port in-port)
(tcp-abandon-port out-port)]
[(string? evt)
(fprintf out-port "~a~n" evt) ;; echoes back received string
(flush-output out-port) ;; flushes the buffer
(loop)] ;; iterates again
[else
(printf "Received a message in mailbox: ~a~n"
(thread-receive))
(loop)])))
(define port-num 4322)
(define (start-server)
(define listener (tcp-listen port-num))
(thread
(lambda ()
(let loop () ;; the server now loops continously listening in for connections
(define-values [in-port out-port] (tcp-accept listener))
;; lets add this open ports to global list of connections
(semaphore-wait fair)
(append connections (list (list in-port out-port)))
(semaphore-post fair)
(serve in-port out-port) ; could be do the greeting in here
(loop)))))
(start-server)
(define client-thread
(thread
(lambda ()
(define-values [in-port out-port] (tcp-connect "localhost" port-num))
(display "first\nsecond\nthird\n" out-port)
(flush-output out-port)
; copy-port will block until EOF is read from in-port
(copy-port in-port (current-output-port)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; author: Ibrahim Mkusa
;; about: print and read concurrently
;; notes: output may need to be aligned and formatted nicely
;; look into
;; https://docs.racket-lang.org/gui/text-field_.html#%28meth._%28%28%28lib._mred%2Fmain..rkt%29._text-field~25%29._get-editor%29%29
;; create custodian for managing all resources
;; so we can shutdown everything at once
;(define guard (make-custodian (current-custodian)))
;(current-custodian guard)
;; reads values continously from stdin and redisplays them
(define (read-loop)
(display (read-line))
(display "\n")
(read-loop)
)
(define input-prompt "input: ")
(define output-prompt "output: ")
;; prompt for username and bind to a variable username
(display "What's your name?\n")
(define username (read-line))
(define usernamei (string-append username ": ")) ;; make username appear nicer in a prompt
;; intelligent read, quits when user types in "quit"
(define (read-loop-i)
;(semaphore-wait fair)
(display usernamei)
(define input (read-line))
;; do something over here with input maybe send it out
;; Tests input if its a quit then kills all threads
;; An if would be better here tbh
(cond ((string=? input "quit") (begin (kill-thread a)
(kill-thread t))))
(display (string-append output-prompt input "\n"))
;(semaphore-post fair)
(read-loop-i)
)
;; print hello world continously
;; "(hello-world)" can be executed as part of background thread
;; that prints in the event there is something in the input port
(define (hello-world)
(sleep (random-integer 0 15)) ;; sleep between 0 and 15 seconds to simulate coms
;; with server
;(semaphore-wait fair)
;; we will retrieve the line printed below from the server
;; at this time we simulate the input from different users
(define what-to-print (random-integer 0 2))
(if (= what-to-print 0)
(display "Doug: What's up, up?\n")
(display "Fred: Looking good, good!\n"))
;(semaphore-post fair)
(hello-world))
(define t (thread (lambda ()
(read-loop-i))))
(define a (thread (lambda ()
(hello-world))))
(thread-wait t) ;; returns prompt back to drracket
;; below doesn't execute
; (sleep 10)
; (kill-thread t)
; (define a (thread (display "hello world!\n")))
; (display "John: hello soso\n")
; (display "Emmanuel: cumbaya!!!!\n")
|