Port groveler ASDF fix from CFFI
[iolib.git] / examples / ex6-server.lisp
blobcfb5ce5a0bb71ec7ce3178d86752d07fdc4db542
1 (in-package :iolib.examples)
3 ;;;; This file was originally written by Peter Keller (psilord@cs.wisc.edu)
4 ;;;; and this code is released under the same license as IOLib.
6 ;;;; This program uses the multiplexer to select on a listening socket
7 ;;;; which when it becomes ready in the read bit, will do a blocking
8 ;;;; accept, and then register a callback then when it is ready will
9 ;;;; do a blocking read and then immediately a blocking write back to
10 ;;;; the same socket. This is not multithreaded and in fact you
11 ;;;; probably don't want to mix threading and the multiplexor together
12 ;;;; with respect to reading/writing to the network sockets.
14 ;; ex-0b
15 ;; This variable represents the multiplexer state.
16 (defvar *ex6-server-event-base*)
17 ;; ex-0e
19 ;; ex-1b
20 ;; This holds any open connections to clients as keys in the table. The values
21 ;; is a list containing the host and port of the connection. We use this to
22 ;; close all connections to the clients, if any, when the server exits. This
23 ;; allows all clients to notice the server had gone away.
24 (defvar *ex6-server-open-connections*)
25 ;; ex-1e
27 ;; ex-2b
28 ;; Set up the server and server clients with the multiplexer
29 (defun run-ex6-server-helper (port)
31 ;; We don't use with-open-socket here since we may need to have a
32 ;; finer control over when we close the server socket.
33 (let ((server (make-socket :connect :passive
34 :address-family :internet
35 :type :stream
36 :ipv6 nil
37 :external-format '(:utf-8 :eol-style :crlf))))
38 (unwind-protect
39 (progn
40 (format t "Created socket: ~A[fd=~A]~%" server (socket-os-fd server))
41 ;; Bind the socket to all interfaces with specified port.
42 (bind-address server +ipv4-unspecified+ :port port :reuse-addr t)
43 (format t "Bound socket: ~A~%" server)
45 ;; start listening on the server socket
46 (listen-on server :backlog 5)
47 (format t "Listening on socket bound to: ~A:~A~%"
48 (local-host server)
49 (local-port server))
50 ;; ex-2e
52 ;; ex-3b
53 ;; Set up the initial listener handler for any incoming clients
54 (set-io-handler *ex6-server-event-base*
55 (socket-os-fd server)
56 :read
57 (make-ex6-server-listener-handler server))
59 ;; keep accepting connections forever.
60 (handler-case
61 (event-dispatch *ex6-server-event-base*)
63 ;; Just in case any handler misses these conditions, we
64 ;; catch them here.
65 (socket-connection-reset-error ()
66 (format t "~A~A~%"
67 "Caught unexpected reset by peer! "
68 "Client connection reset by peer!"))
69 (hangup ()
70 (format t "~A~A~%"
71 "Caught unexpected hangup! "
72 "Client closed connection on write!"))
73 (end-of-file ()
74 (format t "~A~A~%"
75 "Caught unexpected end-of-file! "
76 "Client closed connection on read!"))))
77 ;; ex-3e
79 ;; ex-4b
80 ;; Cleanup expression for uw-p.
81 ;; Ensure the server socket is closed, regardless of how we left
82 ;; the server.
83 (close server))))
84 ;; ex-4e
86 ;; ex-5b
87 ;; When the multiplexer states the server socket is ready for reading
88 ;; it means that we have a client ready to accept. So we accept it and
89 ;; then register the accepted client socket back into the multiplexer
90 ;; with the appropritate echo protocol function.
91 (defun make-ex6-server-listener-handler (socket)
92 (lambda (fd event exception)
94 ;; do a blocking accept, returning nil if no socket
95 (let* ((client (accept-connection socket :wait t)))
96 (when client
97 (multiple-value-bind (who port)
98 (remote-name client)
99 (format t "Accepted a client from ~A:~A~%" who port)
101 ;; save the client connection in case we need to close it later
102 ;; when the server exits.
103 (setf (gethash `(,who ,port) *ex6-server-open-connections*) client)
105 ;; set up an line echo function for the client socket.
106 (set-io-handler *ex6-server-event-base*
107 (socket-os-fd client)
108 :read
109 (make-ex6-server-line-echoer
110 client
112 port
113 (make-ex6-server-disconnector client))))))))
114 ;; ex-5e
116 ;; ex-6b
117 ;; This function returns a function that reads a line, then
118 ;; echoes it right back onto the socket it came from. This is blocking
119 ;; i/o. This code can suffer denial of service attacks like on page
120 ;; 167 of "Unix Network Programming 2nd Edition: Sockets and XTI", by
121 ;; Richard Stevens.
122 (defun make-ex6-server-line-echoer (socket who port disconnector)
123 (format t "Creating line-echoer for ~A:~A~%" who port)
124 (lambda (fd event exception)
125 (handler-case
126 (let ((line (read-line socket))) ;; read a line from the client
127 (format t "Read ~A:~A: ~A~%" who port line)
128 (format socket "~A~%" line) ;; write it the client
129 (finish-output socket)
130 (format t "Wrote ~A:~A: ~A~%" who port line)
132 ;; close the connection to the client if it asked to quit
133 (when (string= line "quit")
134 (format t "Client requested quit!~%")
135 (funcall disconnector who port)))
137 (socket-connection-reset-error ()
138 ;; Handle the usual and common conditions we'll see while
139 ;; talking to a client
140 (format t "Client's connection was reset by peer.~%")
141 (funcall disconnector who port))
143 (hangup ()
144 (format t "Client went away on a write.~%")
145 (funcall disconnector who port))
147 (end-of-file ()
148 (format t "Client went away on a read.~%")
149 (funcall disconnector who port)))))
150 ;; ex-6e
152 ;; ex-7b
153 ;; If we decide we need to disconnect ourselves from the client, this will
154 ;; remove all the handlers and remove the record of our connection from
155 ;; *ex6-server-open-connections*.
156 (defun make-ex6-server-disconnector (socket)
157 (lambda (who port)
158 (format t "Closing connection to ~A:~A~%" who port)
159 (remove-fd-handlers *ex6-server-event-base* (socket-os-fd socket))
160 (close socket)
161 (remhash `(,who ,port) *ex6-server-open-connections*)))
162 ;; ex-7e
164 ;; ex-8b
165 ;; This is the entrance function into this example.
166 (defun run-ex6-server (&key (port *port*))
167 (let ((*ex6-server-open-connections* nil)
168 (*ex6-server-event-base* nil))
169 (unwind-protect
170 (handler-case
171 (progn
172 ;; Clear the open connection table and init the event base
173 (setf *ex6-server-open-connections*
174 (make-hash-table :test #'equalp)
176 *ex6-server-event-base*
177 (make-instance 'event-base))
179 (run-ex6-server-helper port))
181 ;; handle a common signal
182 (socket-address-in-use-error ()
183 (format t "Bind: Address already in use, forget :reuse-addr t?")))
184 ;; ex-8e
186 ;; ex-9b
187 ;; Cleanup form for uw-p
188 ;; Close all open connections to the clients, if any. We do this
189 ;; because when the server goes away we want the clients to know
190 ;; immediately. Sockets are not memory, and can't just be garbage
191 ;; collected whenever. They have to be eagerly closed.
192 (maphash
193 #'(lambda (k v)
194 (format t "Closing a client connection to ~A~%" k)
195 ;; We don't want to signal any conditions on the close...
196 (close v :abort t))
197 *ex6-server-open-connections*)
199 ;; and clean up the multiplexer too!
200 (when *ex6-server-event-base*
201 (close *ex6-server-event-base*))
202 (format t "Server Exited~%")
203 (finish-output))))
204 ;; ex-9e