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)
26 (server :connect
:passive
27 :address-family
:internet
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~%"
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
*
48 (make-ex8-server-listener-handler server
))
50 ;; keep accepting connections forever.
52 (event-dispatch *ex8-event-base
*)
54 (socket-connection-reset-error ()
55 (format t
"Caught unexpected connection reset by peer!~%"))
58 (format t
"Caught unexpected hangup! Cilent closed connection on write!~%"))
60 (format t
"Caught unexpected end-of-file! Client closed connection on read!~%")))))
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
)))
68 (multiple-value-bind (who port
)
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
)
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.
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
91 (set-io-handler *ex8-event-base
*
94 (funcall io-buffer
:read-some-bytes
))
96 (set-io-handler *ex8-event-base
*
99 (funcall io-buffer
:write-some-bytes
))))))))
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
)
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
)
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
))
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.
149 (format t
"Force closing a client connection to ~A~%" k
)
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.~%")