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 (defvar *ex5b-event-base
*)
8 ;; This program differs from ex5a-client in the fact that when the end-of-file
9 ;; is reached for the input, only the write end of the socket is shutdown, this
10 ;; allows inflight data to reach the server.
12 (defun run-ex5b-client-helper (host port
)
14 ;; Create a internet TCP socket under IPV4
15 (let ((socket (make-socket :connect
:active
16 :address-family
:internet
18 :external-format
'(:utf-8
:eol-style
:crlf
)
21 ;; I don't use with-open-socket here since it is the responsibility of the
22 ;; io handlers to close the active socket. If the socket were to be closed
23 ;; in the io handler, then with-open-socket also closed it, another
24 ;; condition will be signaled when with-open-socket tries to finish the
25 ;; output on the socket.
28 ;; do a blocking connect to the echo server on the port.
29 (connect socket
(lookup-hostname host
) :port port
:wait t
)
31 (format t
"Connected to server ~A:~A from my local connection at ~A:~A!~%"
32 (remote-name socket
) (remote-port socket
)
33 (local-name socket
) (local-port socket
))
35 ;; set up the handlers for read and write
36 (set-io-handler *ex5b-event-base
*
39 (make-ex5b-str-cli-read
41 (make-ex5b-client-disconnector socket
)))
43 (set-io-handler *ex5b-event-base
*
46 (make-ex5b-str-cli-write
48 (make-ex5b-client-disconnector socket
)))
51 ;; keep processing input and output on the fd by calling
52 ;; the relevant handlers as the socket becomes ready.
53 (event-dispatch *ex5b-event-base
*)
55 ;; We'll notify the user of the client here something happened,
56 ;; and let the always run expression for the uw-p form take
57 ;; care of closing the socket in case it was left open.
60 "Uncaught hangup. Server closed connection on write!!~%"))
62 (format t
"Uncaught end-of-file. Server closed connection on read!!~%"))))
64 ;; The always run expression from uw-p.
65 ;; Try to clean up if the client aborted badly and left the socket open.
66 ;; It is safe to close an already closed socket
67 (format t
"Client safely closing open socket to server.~%")
68 (close socket
:abort t
))))
71 (defun make-ex5b-str-cli-write (socket disconnector
)
72 ;; When this next function gets called it is because the event dispatcher
73 ;; knows the socket to the server is writable.
74 (lambda (fd event exception
)
75 ;; Get a line from stdin, and send it to the server
77 (let ((line (read-line)))
78 (format socket
"~A~%" line
)
79 (finish-output socket
))
83 "make-ex5b-str-cli-write: User performed end-of-file!~%")
84 ;; Shutdown the write end of my pipe to give the inflight data the
85 ;; ability to reach the server!
87 "make-ex5b-str-cli-write: Shutting down write end of socket!~%")
88 (shutdown socket
:write t
)
89 ;; since we've shut down the write end of the pipe, remove this handler
90 ;; so we can't read more data from *standard-input* and try to write it
92 (funcall disconnector
:write
))
96 "make-ex5b-str-cli-write: server closed connection on write!~%")
97 (funcall disconnector
:close
)))))
100 (defun make-ex5b-str-cli-read (socket disconnector
)
101 ;; When this next function gets called it is because the event dispatcher
102 ;; knows the socket from the server is readable.
103 (lambda (fd event exception
)
104 ;; get a line from the server, and send it to *standard-output*
106 ;; If we send "quit" to the server, it will close its connection to
107 ;; us and we'll notice that with an end-of-file.
108 (let ((line (read-line socket
)))
109 (format t
"~A~%" line
))
112 (format t
"make-ex5b-str-cli-read: server closed connection on read!~%")
113 ;; If the server closed the connection I'm reading from, then we
114 ;; definitely can't write any more to it either, so remove all
115 ;; handlers for this socket and close everything.
116 (funcall disconnector
:close
)))))
118 (defun make-ex5b-client-disconnector (socket)
119 ;; When this function is called, it can be told which callback to remove, if
120 ;; no callbacks are specified, all of them are removed! The socket can be
121 ;; additionally told to be closed.
122 (lambda (&rest events
)
123 (format t
"Disconnecting socket: ~A~%" socket
)
124 (let ((fd (socket-os-fd socket
)))
125 (if (not (intersection '(:read
:write
:error
) events
))
126 (remove-fd-handlers *ex5b-event-base
* fd
:read t
:write t
:error t
)
128 (when (member :read events
)
129 (remove-fd-handlers *ex5b-event-base
* fd
:read t
))
130 (when (member :write events
)
131 (remove-fd-handlers *ex5b-event-base
* fd
:write t
))
132 (when (member :error events
)
133 (remove-fd-handlers *ex5b-event-base
* fd
:error t
)))))
134 ;; and finally if were asked to close the socket, we do so here
135 (when (member :close events
)
138 (defun run-ex5b-client (&key
(host *host
*) (port *port
*))
139 (let ((*ex5b-event-base
* nil
))
142 ;; When the connection gets closed, either intentionally in the client
143 ;; or because the server went away, we want to leave the multiplexer
144 ;; event loop. So, when making the event-base, we explicitly state
145 ;; that we'd like that behavior.
146 (setf *ex5b-event-base
*
147 (make-instance 'iomux
:event-base
:exit-when-empty t
))
151 (run-ex5b-client-helper host port
)
153 ;; handle a commonly signaled error...
154 (socket-connection-refused-error ()
156 "Connection refused to ~A:~A. Maybe the server isn't running?~%"
157 (lookup-hostname "localhost") port
))))
159 ;; ensure we clean up the event base regardless of how we left the client
161 (when *ex5b-event-base
*
162 (close *ex5b-event-base
*))
163 (format t
"Client Exited.~%"))))