Mark ENOLINK and EMULTIHOP as optional
[iolib.git] / examples / ex8-server.lisp
blob0000567a580f0dcdf110d7a98abd44cf07e9a94a
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 has a similar structure to ex7-server, except it
7 ;;;; performs non-blocking i/o on client sockets, keeping an internal
8 ;;;; io-buffer per client.
10 ;; This holds any open connections to clients as keys in the
11 ;; table. The values is a list containing the host and port of the
12 ;; connection. We use this to close all connections to the clients, if
13 ;; any, when the server exits. This allows all clients to notice the
14 ;; server had gone away.
15 (defvar *ex8-open-connections*)
17 ;; This program uses the multiplexer to select on a listening socket
18 ;; which when it becomes ready in the read bit, will do a blocking
19 ;; accept, and then create a closure which acts as a buffer between
20 ;; reading and writing the client data back and forth to the
21 ;; client. The reads use send-to and recv-from reading and writing
22 ;; only what is necessary to write.
24 (defun run-ex8-server-helper (port)
25 (with-open-socket
26 (server :connect :passive
27 :address-family :internet
28 :type :stream
29 :ipv6 nil)
31 (format t "Created socket: ~A[fd=~A]~%" server (socket-os-fd server))
33 ;; Bind the socket to all interfaces with specified port.
34 (bind-address server +ipv4-unspecified+ :port port :reuse-addr t)
35 (format t "Bound socket: ~A~%" server)
37 ;; start listening on the server socket
38 (listen-on server :backlog 5)
39 (format t "Listening on socket bound to: ~A:~A~%"
40 (local-host server)
41 (local-port server))
43 ;; Set up the initial listener handler for any incoming clients
44 ;; What kind of error checking do I need to do here?
45 (set-io-handler *ex8-event-base*
46 (socket-os-fd server)
47 :read
48 (make-ex8-server-listener-handler server))
50 ;; keep accepting connections forever.
51 (handler-case
52 (event-dispatch *ex8-event-base*)
54 (socket-connection-reset-error ()
55 (format t "Caught unexpected connection reset by peer!~%"))
57 (hangup ()
58 (format t "Caught unexpected hangup! Cilent closed connection on write!~%"))
59 (end-of-file ()
60 (format t "Caught unexpected end-of-file! Client closed connection on read!~%")))))
62 ;; ex-0b
63 (defun make-ex8-server-listener-handler (socket)
64 (lambda (fd event exception)
65 ;; do a blocking accept, returning nil if no socket
66 (let* ((client (accept-connection socket :wait t)))
67 (when client
68 (multiple-value-bind (who port)
69 (remote-name client)
70 (format t "Accepted a client from ~A:~A~%" who port)
72 ;; save the client connection in case we need to close it later.
73 (setf (gethash `(,who ,port) *ex8-open-connections*) client)
74 ;; ex-0e
76 ;; ex-1b
77 ;; We make an io-buffer, which takes care of reading from the
78 ;; socket and echoing the information it read back onto the
79 ;; socket. The buffer takes care of this with two internal
80 ;; handlers, a read handler and a write handler.
81 (let ((io-buffer
82 (make-ex8-io-buffer client who port
83 (make-ex8-server-disconnector client))))
85 ;; set up an unsigned byte echo function for the
86 ;; client socket. The internals of the buffer will
87 ;; perform the appropriate registration/unregistration of
88 ;; the required handlers at the right time depending upon
89 ;; data availability.
91 (set-io-handler *ex8-event-base*
92 (socket-os-fd client)
93 :read
94 (funcall io-buffer :read-some-bytes))
96 (set-io-handler *ex8-event-base*
97 (socket-os-fd client)
98 :write
99 (funcall io-buffer :write-some-bytes))))))))
100 ;; ex-1e
102 (defun make-ex8-server-disconnector (socket)
103 ;; When this function is called, it can be told which callback to remove, if
104 ;; no callbacks are specified, all of them are removed! The socket can be
105 ;; additionally told to be closed.
106 (lambda (who port &rest events)
107 (let ((fd (socket-os-fd socket)))
108 (if (not (intersection '(:read :write :error) events))
109 (remove-fd-handlers *ex8-event-base* fd :read t :write t :error t)
110 (progn
111 (when (member :read events)
112 (remove-fd-handlers *ex8-event-base* fd :read t))
113 (when (member :write events)
114 (remove-fd-handlers *ex8-event-base* fd :write t))
115 (when (member :error events)
116 (remove-fd-handlers *ex8-event-base* fd :error t)))))
117 ;; and finally if were asked to close the socket, we do so here
118 (when (member :close events)
119 (format t "Closing connection to ~A:~A~%" who port)
120 (finish-output)
121 (close socket)
122 (remhash `(,who ,port) *ex8-open-connections*))))
125 ;; If for whatever reason the server has to stop running, we ensure that all
126 ;; open connections are closed.
127 (defun run-ex8-server (&key (port *port*))
128 (let ((*ex8-open-connections* nil)
129 (*ex8-event-base* nil))
130 (unwind-protect
131 (handler-case
132 (progn
133 (setf *ex8-open-connections* (make-hash-table :test #'equalp)
134 *ex8-event-base* (make-instance 'event-base))
136 (run-ex8-server-helper port))
138 ;; handle some common signals
139 (socket-address-in-use-error ()
140 (format t "Bind: Address already in use, forget :reuse-addr t?")))
142 ;; Cleanup form for uw-p
143 ;; Close all open connections to the clients, if any. We do this
144 ;; because when the server goes away we want the clients to know
145 ;; immediately. Sockets are not memory, and can't just be garbage
146 ;; collected whenever. They have to be eagerly closed.
147 (maphash
148 #'(lambda (k v)
149 (format t "Force closing a client connection to ~A~%" k)
150 (close v :abort t))
151 *ex8-open-connections*)
153 ;; and clean up the event-base too!
154 (when *ex8-event-base*
155 (close *ex8-event-base*))
156 (format t "Server Exited.~%")
157 (finish-output))))