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.
15 ;; This variable represents the multiplexer state.
16 (defvar *ex6-server-event-base
*)
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
*)
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
37 :external-format
'(:utf-8
:eol-style
:crlf
))))
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~%"
53 ;; Set up the initial listener handler for any incoming clients
54 (set-io-handler *ex6-server-event-base
*
57 (make-ex6-server-listener-handler server
))
59 ;; keep accepting connections forever.
61 (event-dispatch *ex6-server-event-base
*)
63 ;; Just in case any handler misses these conditions, we
65 (socket-connection-reset-error ()
67 "Caught unexpected reset by peer! "
68 "Client connection reset by peer!"))
71 "Caught unexpected hangup! "
72 "Client closed connection on write!"))
75 "Caught unexpected end-of-file! "
76 "Client closed connection on read!"))))
80 ;; Cleanup expression for uw-p.
81 ;; Ensure the server socket is closed, regardless of how we left
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
)))
97 (multiple-value-bind (who port
)
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
)
109 (make-ex6-server-line-echoer
113 (make-ex6-server-disconnector client
))))))))
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
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
)
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
))
144 (format t
"Client went away on a write.~%")
145 (funcall disconnector who port
))
148 (format t
"Client went away on a read.~%")
149 (funcall disconnector who port
)))))
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)
158 (format t
"Closing connection to ~A:~A~%" who port
)
159 (remove-fd-handlers *ex6-server-event-base
* (socket-os-fd socket
))
161 (remhash `(,who
,port
) *ex6-server-open-connections
*)))
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
))
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?")))
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.
194 (format t
"Closing a client connection to ~A~%" k
)
195 ;; We don't want to signal any conditions on the close...
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~%")