Port groveler ASDF fix from CFFI
[iolib.git] / examples / ex7-server.lisp
blob80af90174e7848a1ee4f8764d9f0048c28b88eab
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 programs differs from ex6-server in that an explicit
7 ;;;; io-buffer is created and used to separate the read handler from
8 ;;;; the write handler. Also this server is aware if the client
9 ;;;; closesd the write end of its socket to the server, but still
10 ;;;; expects to be reading inflight data in the server In ex6-server,
11 ;;;; if the read was ready to the client, but not the write, we would
12 ;;;; block. In this server, the read would happen, and we could move
13 ;;;; onto another client of the first one would have blocked while
14 ;;;; writing the response. This client is slightly more "ready for
15 ;;;; I/O" friendly, but all i/o in in fact blocking.
17 ;; This holds any open connections to clients as keys in the
18 ;; table. The values is a list containing the host and port of the
19 ;; connection. We use this to close all connections to the clients, if
20 ;; any, when the server exits. This allows all clients to notice the
21 ;; server had gone away.
22 (defvar *ex7-open-connections*)
24 ;; This program uses the multiplexer to select on a listening socket
25 ;; which when it becomes ready in the read bit, will do a blocking
26 ;; accept, and then create a closure which acts as a buffer between
27 ;; reading and writing the client data back and forth to the
28 ;; client. The reads use send-to and recv-from reading and writing
29 ;; only what is necessary to write.
31 ;; This makes a closure which contains a buffer filled by recv-from
32 ;; and sent be send-to. When the buffer is empty, the write handler is
33 ;; removed.
35 (defun run-ex7-server-helper (port)
36 (with-open-socket
37 (server :connect :passive
38 :address-family :internet
39 :type :stream
40 :ipv6 nil
41 :external-format '(:utf-8 :eol-style :crlf))
43 (format t "Created socket: ~A[fd=~A]~%" server (socket-os-fd server))
45 ;; Bind the socket to all interfaces with specified port.
46 (bind-address server +ipv4-unspecified+ :port port :reuse-addr t)
47 (format t "Bound socket: ~A~%" server)
49 ;; start listening on the server socket
50 (listen-on server :backlog 5)
51 (format t "Listening on socket bound to: ~A:~A~%"
52 (local-host server)
53 (local-port server))
55 ;; Set up the initial listener handler for any incoming clients
56 ;; What kind of error checking do I need to do here?
57 (set-io-handler *ex7-event-base*
58 (socket-os-fd server)
59 :read
60 (make-ex7-server-listener-handler server))
62 ;; keep accepting connections forever.
63 (handler-case
64 (event-dispatch *ex7-event-base*)
66 (socket-connection-reset-error ()
67 (format t "Caught unexpected connection reset by peer!~%"))
69 (hangup ()
70 (format t "Caught unexpected hangup! Cilent closed connection on write!"))
71 (end-of-file ()
72 (format t "Caught unexpected end-of-file! Client closed connection on read!")))))
74 ;; ex-0b
75 ;; Create the listener closure which accepts the client and registers the
76 ;; buffer functions with it.
77 (defun make-ex7-server-listener-handler (socket)
78 (lambda (fd event exception)
79 ;; do a blocking accept, returning nil if no socket
80 (let* ((client (accept-connection socket :wait t)))
81 (when client
82 (multiple-value-bind (who port)
83 (remote-name client)
84 (format t "Accepted a client from ~A:~A~%" who port)
86 ;; save the client connection in case we need to close it later.
87 (setf (gethash `(,who ,port) *ex7-open-connections*) client)
89 ;; We make an io-buffer, which takes care of reading from the
90 ;; socket and echoing the information it read back onto the
91 ;; socket. The buffer takes care of this with two internal
92 ;; handlers, a read handler and a write handler.
93 (let ((io-buffer
94 (make-ex7-io-buffer client who port
95 (make-ex7-server-disconnector client))))
97 ;; set up an line echo function for the client socket. The
98 ;; internals of the buffer will perform the appropriate
99 ;; registration/unregistration of the required handlers at
100 ;; the right time depending upon data availability.
102 (set-io-handler *ex7-event-base*
103 (socket-os-fd client)
104 :read
105 (funcall io-buffer :read-a-line))
107 (set-io-handler *ex7-event-base*
108 (socket-os-fd client)
109 :write
110 (funcall io-buffer :write-a-line))))))))
111 ;; ex-0e
113 ;; ex-1b
114 (defun make-ex7-server-disconnector (socket)
115 ;; When this function is called, it can be told which callback to remove, if
116 ;; no callbacks are specified, all of them are removed! The socket can be
117 ;; additionally told to be closed.
118 (lambda (who port &rest events)
119 (let ((fd (socket-os-fd socket)))
120 (if (not (intersection '(:read :write :error) events))
121 (remove-fd-handlers *ex7-event-base* fd :read t :write t :error t)
122 (progn
123 (when (member :read events)
124 (remove-fd-handlers *ex7-event-base* fd :read t))
125 (when (member :write events)
126 (remove-fd-handlers *ex7-event-base* fd :write t))
127 (when (member :error events)
128 (remove-fd-handlers *ex7-event-base* fd :error t)))))
129 ;; and finally if were asked to close the socket, we do so here
130 (when (member :close events)
131 (format t "Closing connection to ~A:~A~%" who port)
132 (finish-output)
133 (close socket)
134 (remhash `(,who ,port) *ex7-open-connections*))))
135 ;; ex-1e
137 ;; If for whatever reason the server has to stop running, we ensure that all
138 ;; open connections are closed.
139 (defun run-ex7-server (&key (port *port*))
140 (let ((*ex7-open-connections* nil)
141 (*ex7-event-base* nil))
142 (unwind-protect
143 (handler-case
144 (progn
145 (setf *ex7-open-connections* (make-hash-table :test #'equalp)
146 *ex7-event-base* (make-instance 'event-base))
148 (run-ex7-server-helper port))
150 ;; handle some common signals
151 (socket-address-in-use-error ()
152 (format t "Bind: Address already in use, forget :reuse-addr t?")))
154 ;; Cleanup form for uw-p
155 ;; Close all open connections to the clients, if any. We do this because
156 ;; when the server goes away we want the clients to know immediately. Sockets
157 ;; are not memory, and can't just be garbage collected whenever. They have
158 ;; to be eagerly closed.
159 (maphash
160 #'(lambda (k v)
161 (format t "Force closing a client connection to ~A~%" k)
162 (close v :abort t))
163 *ex7-open-connections*)
165 ;; and clean up the event-base too!
166 (when *ex7-event-base*
167 (close *ex7-event-base*))
168 (format t "Server Exited.~%")
169 (finish-output))))