Mark ENOLINK and EMULTIHOP as optional
[iolib.git] / examples / ex8-buffer.lisp
blob46e99f9d79a652bcb4cb6dadee6542d574be30a0
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 buffer generator creates a closure which holds an array that can
7 ;;;; hold up to a maximum number of bytes read from the socket. The bytes are
8 ;;;; not inspected and simply stored in an array. If there are bytes to write
9 ;;;; out from the array the write handler is registered and they are written.
10 ;;;; In this manner does the written data "follow" the read data. When the
11 ;;;; end of the buffer is reach via a write, we start over at the beginning
12 ;;;; again.
14 ;;;; This io-buffer reads and writes in a nonblocking fashion on the
15 ;;;; socket and stores partial reads and writes in an internal buffer.
17 ;; The event dispatcher
18 (defvar *ex8-event-base*)
20 ;; ex-0b
21 (defun make-ex8-io-buffer (socket who port disconnector &key (max-bytes 16384))
22 (let ((echo-buf (make-array max-bytes :element-type 'unsigned-byte))
23 (read-index 0)
24 (write-index 0)
25 (read-handler-registered nil)
26 (write-handler-registered nil)
27 (eof-seen nil))
28 ;; ex-0e
29 ;; ex-1b
30 (labels
31 ;; This is the function responsible for reading bytes from the client.
32 ((read-some-bytes (fd event exception)
33 (handler-case
34 (progn
35 ;; Read however much we are able.
36 (multiple-value-bind (buf bytes-read)
37 (receive-from socket
38 :buffer echo-buf
39 :start read-index
40 :end max-bytes)
42 ;; Unlike read-ing from a stream, receive-from
43 ;; returns zero on an end-of-file read, so we turn
44 ;; around and signal that condition so our
45 ;; handler-case can deal with it properly like our
46 ;; other examples.
47 (when (zerop bytes-read)
48 (error 'end-of-file))
50 (format t "Read ~A bytes from ~A:~A~%" bytes-read who port)
51 (incf read-index bytes-read))
53 ;; Register the write handler if there is data to
54 ;; write.
56 ;; Then, try to write some data to the socket right
57 ;; away even though it might not be ready simply to
58 ;; avoid another go around. The write-some-bytes
59 ;; function must be able to catch econnreset because
60 ;; this connection may be closed at the time of this
61 ;; call. Normally, if the multiplexer has told me I
62 ;; could write then it'd be ok, but since this write
63 ;; is outside of the multiplexer and an optimization,
64 ;; it needs to check.
65 (when (/= write-index read-index)
66 (unless write-handler-registered
67 (set-io-handler *ex8-event-base*
68 (socket-os-fd socket)
69 :write
70 #'write-some-bytes)
71 (setf write-handler-registered t))
73 ;; See if I can write it right away!
74 (write-some-bytes fd :write nil))
76 ;; If I'm out of room to store more data then remove
77 ;; myself from the io handler. When the write handler
78 ;; notices that it has finished writing everything,
79 ;; all indicies get set back to zero and the write
80 ;; handler removes itself. If write-some-bytes in
81 ;; the call above worked, then read-index might not
82 ;; equal max-bytes when this line of code gets
83 ;; executed.
84 (when (= read-index max-bytes)
85 (funcall disconnector who port :read)
86 (setf read-handler-registered nil)))
88 (socket-connection-reset-error ()
89 ;; Handle the client sending a reset.
90 (format t "Client ~A:~A: connection reset by peer.~%" who port)
91 (funcall disconnector who port :close))
93 (end-of-file ()
94 ;; When we get an end of file, that doesn't necessarily
95 ;; mean the client went away, it could just mean that
96 ;; the client performed a shutdown on the write end of
97 ;; its socket and it is expecting the data stored in
98 ;; the server to be written to it. However, if there
99 ;; is nothing left to write and our read end is closed,
100 ;; we shall consider it that the client went away and
101 ;; close the connection.
102 (format t "Client ~A:~A produced end-of-file on a read.~%"
103 who port)
104 (if (= read-index write-index)
105 (funcall disconnector who port :close)
106 (progn
107 (funcall disconnector who port :read)
108 (setf read-handler-registered nil)
109 (setf eof-seen t))))))
110 ;; ex-1e
112 ;; ex-2b
113 ;; This is the function responsible for writing bytes to the client.
114 (write-some-bytes (fd event exception)
115 (handler-case
116 (progn
117 ;; If there is data to be written, write it. NOTE:
118 ;; There is often no indication of failure to write
119 ;; with send-to. If I'm writing to a closed (by the
120 ;; client) socket, it could be that send-to tells me
121 ;; nothing is wrong and returns the number of bytes
122 ;; wrotten. In this case, nothing was written but we
123 ;; have no way of knowing. Usually in this case, the
124 ;; read handler will get a 0 bytes read on the socket
125 ;; and we can know the connection is broken.
126 (when (> read-index write-index)
127 (let ((wrote-bytes (send-to socket echo-buf
128 :start write-index
129 :end read-index)))
130 (format t "Wrote ~A bytes to ~A:~A~%" wrote-bytes who port)
131 (incf write-index wrote-bytes)))
133 ;; If we see we're out of data to write and we saw an eof,
134 ;; then close the connection, we're done. If we didn't see an
135 ;; eof, then unregister the write handler and reregister the
136 ;; read handler to get more data. If the buffer indices
137 ;; are at the very end, reset them to the beginning.
138 (when (= read-index write-index)
139 (if eof-seen
140 (funcall disconnector who port :close)
141 (progn
143 ;; nothing more to write, so unregister writer
144 (funcall disconnector who port :write)
145 (setf write-handler-registered nil)
147 ;; If we're at the end of the buffer, move to the
148 ;; beginning so there is more room for data.
149 (when (= read-index write-index max-bytes)
150 (setf read-index 0
151 write-index 0))
153 ;; Reregister the read handler to get more data
154 (unless read-handler-registered
155 (set-io-handler *ex8-event-base*
156 (socket-os-fd socket)
157 :read
158 #'read-some-bytes)
159 (setf read-handler-registered t))))))
161 (socket-connection-reset-error ()
162 ;; If for somer eaon the client reset the network connection,
163 ;; we'll get this signal.
164 (format t "Client ~A:~A: connection reset by peer.~%" who port)
165 (funcall disconnector who port :close))
167 (isys:ewouldblock ()
168 ;; Sometimes this happens on a write even though it
169 ;; might have been marked as ready. Also we might have
170 ;; asked to write on an unknown status socket. Ignore
171 ;; it and we will try again later.
172 (format t "write-some-bytes: ewouldblock~%")
173 nil)
175 (isys:epipe ()
176 ;; In this server, if the client doesn't accept data,
177 ;; it also means it will never send us data again. So
178 ;; close the connection for good.
179 (format t "Client ~A:~A got hangup on write.~%" who port)
180 (funcall disconnector who port :close)))))
181 ;; ex-2e
183 ;; ex-3b
184 ;; This is the function returned from make-ex8-io-buffer which
185 ;; allows us access to the read/writer in the scope of the
186 ;; closure. We will ask for the correct functions when setting
187 ;; up the io handlers. NOTE: By simply asking for the handler,
188 ;; I've assumed it is to be immediately put into an iolib event
189 ;; handler. This is why they are considered registered at this
190 ;; point.
191 (lambda (msg)
192 (cond
193 ((equalp msg :read-some-bytes)
194 (setf read-handler-registered t)
195 #'read-some-bytes)
196 ((equalp msg :write-some-bytes)
197 (setf write-handler-registered t)
198 #'write-some-bytes)
200 (error "make-ex8-buffer: Please supply :read-some-bytes or :write-some-bytes~%")))))))
202 ;; ex-3e