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
35 (defun run-ex7-server-helper (port)
37 (server :connect
:passive
38 :address-family
:internet
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~%"
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
*
60 (make-ex7-server-listener-handler server
))
62 ;; keep accepting connections forever.
64 (event-dispatch *ex7-event-base
*)
66 (socket-connection-reset-error ()
67 (format t
"Caught unexpected connection reset by peer!~%"))
70 (format t
"Caught unexpected hangup! Cilent closed connection on write!"))
72 (format t
"Caught unexpected end-of-file! Client closed connection on read!")))))
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
)))
82 (multiple-value-bind (who port
)
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.
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
)
105 (funcall io-buffer
:read-a-line
))
107 (set-io-handler *ex7-event-base
*
108 (socket-os-fd client
)
110 (funcall io-buffer
:write-a-line
))))))))
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
)
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
)
134 (remhash `(,who
,port
) *ex7-open-connections
*))))
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
))
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.
161 (format t
"Force closing a client connection to ~A~%" k
)
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.~%")