Mark ENOLINK and EMULTIHOP as optional
[iolib.git] / examples / ex5b-client.lisp
blob13b9b2156bbd05de0547b1fd5cf5e2b6b477e05d
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
17 :type :stream
18 :external-format '(:utf-8 :eol-style :crlf)
19 :ipv6 nil)))
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.
26 (unwind-protect
27 (progn
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*
37 (socket-os-fd socket)
38 :read
39 (make-ex5b-str-cli-read
40 socket
41 (make-ex5b-client-disconnector socket)))
43 (set-io-handler *ex5b-event-base*
44 (socket-os-fd socket)
45 :write
46 (make-ex5b-str-cli-write
47 socket
48 (make-ex5b-client-disconnector socket)))
50 (handler-case
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.
58 (hangup ()
59 (format t
60 "Uncaught hangup. Server closed connection on write!!~%"))
61 (end-of-file ()
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))))
70 ;; ex-0b
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
76 (handler-case
77 (let ((line (read-line)))
78 (format socket "~A~%" line)
79 (finish-output socket))
81 (end-of-file ()
82 (format t
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!
86 (format t
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
91 ;; it to the server.
92 (funcall disconnector :write))
94 (hangup ()
95 (format t
96 "make-ex5b-str-cli-write: server closed connection on write!~%")
97 (funcall disconnector :close)))))
98 ;; ex-0e
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*
105 (handler-case
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))
111 (end-of-file ()
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)
127 (progn
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)
136 (close socket))))
138 (defun run-ex5b-client (&key (host *host*) (port *port*))
139 (let ((*ex5b-event-base* nil))
140 (unwind-protect
141 (progn
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))
149 (handler-case
151 (run-ex5b-client-helper host port)
153 ;; handle a commonly signaled error...
154 (socket-connection-refused-error ()
155 (format t
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
160 ;; algorithm
161 (when *ex5b-event-base*
162 (close *ex5b-event-base*))
163 (format t "Client Exited.~%"))))