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 example uses the event-dispatcher to know when there is data
7 ;;;; ready for reading and writing to the server. However, the actual
8 ;;;; reads and writes are nonblocking i/o. We don't use
9 ;;;; with-open-socket here since the disconnector function associated
10 ;;;; with each handler may close the socket and we wouldn't want to
11 ;;;; close it again in a manner which might signal yet another
12 ;;;; condition--which is what the cleanup form of with-open-socket
16 ;; This will be an instance of the multiplexer.
17 (defvar *ex5a-event-base
*)
20 ;; in batch mode, this will cut the connection to the server when end of input
21 ;; is found, which kills the inflight data to the server. Problematic.
24 (defun run-ex5a-client-helper (host port
)
25 ;; Create a internet TCP socket under IPV4
26 ;; We specifically do not use with-open-socket here since that form is
27 ;; more suited for synchronous i/o on one socket. Since we do not use that
28 ;; form, it is up to the handlers to decide to remove and close the socket
29 ;; when the connection to the server should be closed.
30 (let ((socket (make-socket :connect
:active
31 :address-family
:internet
33 :external-format
'(:utf-8
:eol-style
:crlf
)
37 ;; Don't use with-open-socket here since it is the responsibility
38 ;; of the io handlers to close the active socket. If the socket
39 ;; were to be closed in the io handler, then with-open-socket also
40 ;; closed it, another condition will be signaled when
41 ;; with-open-socket tries to finish the output on the socket.
45 ;; do a blocking connect to the echo server on the port.
46 (connect socket
(lookup-hostname host
) :port port
:wait t
)
48 (format t
"Connected to server ~A:~A from my local connection at ~A:~A!~%"
49 (remote-host socket
) (remote-port socket
)
50 (local-host socket
) (local-port socket
))
52 ;; set up the handlers for read and write
53 (set-io-handler *ex5a-event-base
*
56 (make-ex5a-str-cli-read
58 (make-ex5a-client-disconnector socket
)))
60 (set-io-handler *ex5a-event-base
*
63 (make-ex5a-str-cli-write
65 (make-ex5a-client-disconnector socket
)))
68 ;; keep processing input and output on the fd by
69 ;; calling the relevant handlers as the socket becomes
70 ;; ready. The relevant handlers will take care of
71 ;; closing the socket at appropriate times.
72 (event-dispatch *ex5a-event-base
*)
74 ;; We'll notify the user of the client if a handler missed
75 ;; catching common conditions.
77 (format t
"Uncaught hangup. Server closed connection on write!%"))
79 (format t
"Uncaught end-of-file. Server closed connection on read!%"))))
84 ;; Cleanup expression for uw-p.
85 ;; Try to clean up if the client aborted badly and left the socket open.
86 ;; It is safe to call close mutiple times on a socket.
87 ;; However, we don't want to finish-output on the socket since that
88 ;; might signal another condition since the io handler already closed
90 (format t
"Client safely closing open socket to server.~%")
91 (close socket
:abort t
))))
95 (defun make-ex5a-str-cli-write (socket disconnector
)
96 ;; When this next function gets called it is because the event dispatcher
97 ;; knows the socket to the server is writable.
98 (lambda (fd event exception
)
99 ;; Get a line from stdin, and send it to the server
101 (let ((line (read-line)))
102 (format socket
"~A~%" line
)
103 (finish-output socket
))
106 (format t
"make-ex5a-str-cli-write: User performed end-of-file!~%")
107 (funcall disconnector
:close
))
111 "make-ex5a-str-cli-write: server closed connection on write!~%")
112 (funcall disconnector
:close
)))))
116 (defun make-ex5a-str-cli-read (socket disconnector
)
117 ;; When this next function gets called it is because the event dispatcher
118 ;; knows the socket from the server is readable.
119 (lambda (fd event exception
)
120 ;; get a line from the server, and send it to *standard-output*
122 ;; If we send "quit" to the server, it will close its connection to
123 ;; us and we'll notice that with an end-of-file.
124 (let ((line (read-line socket
)))
125 (format t
"~A~%" line
)
129 (format t
"make-ex5a-str-cli-read: server closed connection on read!~%")
130 (funcall disconnector
:close
)))))
134 (defun make-ex5a-client-disconnector (socket)
135 ;; When this function is called, it can be told which callback to remove, if
136 ;; no callbacks are specified, all of them are removed! The socket can be
137 ;; additionally told to be closed.
138 (lambda (&rest events
)
139 (format t
"Disconnecting socket: ~A~%" socket
)
140 (let ((fd (socket-os-fd socket
)))
141 (if (not (intersection '(:read
:write
:error
) events
))
142 (remove-fd-handlers *ex5a-event-base
* fd
:read t
:write t
:error t
)
144 (when (member :read events
)
145 (remove-fd-handlers *ex5a-event-base
* fd
:read t
))
146 (when (member :write events
)
147 (remove-fd-handlers *ex5a-event-base
* fd
:write t
))
148 (when (member :error events
)
149 (remove-fd-handlers *ex5a-event-base
* fd
:error t
)))))
150 ;; and finally if were asked to close the socket, we do so here
151 (when (member :close events
)
152 (close socket
:abort t
))))
156 ;; This is the entry point for this example.
157 (defun run-ex5a-client (&key
(host *host
*) (port *port
*))
158 (let ((*ex5a-event-base
* nil
))
161 ;; When the connection gets closed, either intentionally in the client
162 ;; or because the server went away, we want to leave the multiplexer
163 ;; event loop. So, when making the event-base, we explicitly state
164 ;; that we'd like that behavior.
165 (setf *ex5a-event-base
*
166 (make-instance 'iomux
:event-base
:exit-when-empty t
))
168 (run-ex5a-client-helper host port
)
170 ;; handle a commonly signaled error...
171 (socket-connection-refused-error ()
172 (format t
"Connection refused to ~A:~A. Maybe the server isn't running?~%"
173 (lookup-hostname host
) port
))))
177 ;; Cleanup form for uw-p
178 ;; ensure we clean up the event base regardless of how we left the client
180 (when *ex5a-event-base
*
181 (close *ex5a-event-base
*))
182 (format t
"Client Exited.~%")