Update ISYS::%PTSNAME to latest libfixposix
[iolib.git] / examples / ex5a-client.lisp
blobeba98f5c383fae75cf527f4b5528f85964f056e8
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
13 ;;;; will do.
15 ;; ex-0b
16 ;; This will be an instance of the multiplexer.
17 (defvar *ex5a-event-base*)
18 ;; ex-0e
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.
23 ;; ex-1b
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
32 :type :stream
33 :external-format '(:utf-8 :eol-style :crlf)
34 :ipv6 nil)))
35 ;; ex-1e
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.
42 ;; ex-2b
43 (unwind-protect
44 (progn
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*
54 (socket-os-fd socket)
55 :read
56 (make-ex5a-str-cli-read
57 socket
58 (make-ex5a-client-disconnector socket)))
60 (set-io-handler *ex5a-event-base*
61 (socket-os-fd socket)
62 :write
63 (make-ex5a-str-cli-write
64 socket
65 (make-ex5a-client-disconnector socket)))
67 (handler-case
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.
76 (hangup ()
77 (format t "Uncaught hangup. Server closed connection on write!%"))
78 (end-of-file ()
79 (format t "Uncaught end-of-file. Server closed connection on read!%"))))
80 ;; ex-2e
82 ;; ex-3b
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
89 ;; the socket.
90 (format t "Client safely closing open socket to server.~%")
91 (close socket :abort t))))
92 ;; ex-3e
94 ;; ex-4b
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
100 (handler-case
101 (let ((line (read-line)))
102 (format socket "~A~%" line)
103 (finish-output socket))
105 (end-of-file ()
106 (format t "make-ex5a-str-cli-write: User performed end-of-file!~%")
107 (funcall disconnector :close))
109 (hangup ()
110 (format t
111 "make-ex5a-str-cli-write: server closed connection on write!~%")
112 (funcall disconnector :close)))))
113 ;; ex-4e
115 ;; ex-5b
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*
121 (handler-case
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)
126 (finish-output))
128 (end-of-file ()
129 (format t "make-ex5a-str-cli-read: server closed connection on read!~%")
130 (funcall disconnector :close)))))
131 ;; ex-5e
133 ;; ex-6b
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)
143 (progn
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))))
153 ;; ex-6e
155 ;; ex-7b
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))
159 (unwind-protect
160 (progn
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))
167 (handler-case
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))))
174 ;; ex-7e
176 ;; ex-8b
177 ;; Cleanup form for uw-p
178 ;; ensure we clean up the event base regardless of how we left the client
179 ;; algorithm
180 (when *ex5a-event-base*
181 (close *ex5a-event-base*))
182 (format t "Client Exited.~%")
183 (finish-output))))
184 ;; ex-8e