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 function returns a closure from which I can ask for a
7 ;;;; reader/writer function that the event-dispatcher can use. When
8 ;;;; constructing the buffer, which consists of a queue of lines, I
9 ;;;; specify the socket upon which the buffer is responsible and how
10 ;;;; bytes I'm willing to buffer. Obviously, if a line is very much
11 ;;;; bigger than the max-bytes I have, I have to read the whole thing
12 ;;;; due to read-line and the blocking i/o requirement.
14 ;; The event dispatcher
15 (defvar *ex7-event-base
*)
18 (defun make-ex7-io-buffer (socket who port disconnector
&key
(max-bytes 4096))
19 (let ((line-queue (make-queue))
20 (bytes-left-to-write 0)
21 (read-handler-registered nil
)
22 (write-handler-registered nil
)
27 ;; If this function notices that there is data to write, it will
28 ;; set the io-handler on the socket for the write handler.
29 ;; If the function notices it has read >= than the max-bytes
30 ;; it will remove itself from the handler *after* ensuring the
31 ;; write handler is set up properly.
32 ((read-a-line (fd event exception
)
34 (let ((line (format nil
"~A~%" (read-line socket
)))) ; add a \n
35 (format t
"Read from ~A:~A: ~A" who port line
)
36 (enqueue line line-queue
)
37 (incf bytes-left-to-write
(length line
))
39 (when (> bytes-left-to-write
0)
40 ;; If the write handler isn't registered, then do
41 ;; it now since I have data to write.
42 (unless write-handler-registered
43 (set-io-handler *ex7-event-base
*
47 (setf write-handler-registered t
)))
49 ;; Now, if there is more data than I should be
50 ;; reading, remove myself from the io handler. When
51 ;; the write handler notices that, after writing some
52 ;; data, more of it can be read, it will reregister
53 ;; the io handler for the read socket.
54 (when (>= bytes-left-to-write max-bytes
)
55 (funcall disconnector who port
:read
)
56 (setf read-handler-registered nil
)))
58 (socket-connection-reset-error ()
59 ;; If the client resets its connection, we close
61 (format t
"Client ~A:~A: Connection reset by peer~%" who port
)
62 (funcall disconnector who port
:close
))
65 ;; When we get an end of file, that doesn't necessarily
66 ;; mean the client went away, it could just mean that
67 ;; the client performed a shutdown on the write end of
68 ;; its socket and it is expecting the data stored in
69 ;; the server to be written to it. However, if there
70 ;; is nothing left to write and our read end is close,
71 ;; we shall consider it that the client went away and
72 ;; close the connection.
73 (format t
"Client ~A:~A produced end-of-file on a read.~%"
75 (if (zerop bytes-left-to-write
)
76 (funcall disconnector who port
:close
)
78 (funcall disconnector who port
:read
)
79 (setf read-handler-registered nil
)
80 (setf eof-seen t
))))))
84 ;; This function will notice that if it has written enough bytes to
85 ;; bring the bytes-left-to-write under max-bytes, it will re-register
86 ;; the reader io handler. If there is no data to write, it will,
87 ;; after ensuring the read handler is registered, unregister itself
88 ;; as to not be called constantly on a write ready socket with no
90 (write-a-line (fd event exception
)
93 ;; If we have something to write to the client, do so.
94 (when (> bytes-left-to-write
0)
95 (let ((line (dequeue line-queue
)))
96 (format socket
"~A" line
) ;; newline is in the string.
97 (finish-output socket
)
98 (format t
"Wrote to ~A:~A: ~A" who port line
)
99 (decf bytes-left-to-write
(length line
))))
101 ;; If we see we've fallen below the max-bytes mark,
102 ;; re-register the read handler to get more data for
103 ;; us. However, don't reregister the read handler if
104 ;; we've seen that the client closed our read end of
106 (when (< bytes-left-to-write max-bytes
)
107 (unless (or eof-seen read-handler-registered
)
108 (set-io-handler *ex7-event-base
*
109 (socket-os-fd socket
)
112 (setf read-handler-registered t
)))
114 ;; If we notice that we don't have any data to write
115 ;; AND have seen the end of file from the client,
116 ;; then we close the connection to the client since
117 ;; it will never speak to us again and we're done
120 ;; If notice we've written all of our data and there
121 ;; might be more to do later, then unregister the
122 ;; write handler so we don't get called
123 ;; unnecesarily. This might mean that sometimes we'll
124 ;; have to make an extra trip through the
125 ;; event-dispatcher to perform the write if we read
126 ;; more from the client and it reregisters us.
127 (when (zerop bytes-left-to-write
)
129 (funcall disconnector who port
:close
)
131 (funcall disconnector who port
:write
)
132 (setf write-handler-registered nil
)))))
134 (socket-connection-reset-error ()
135 ;; If I happen to get a reset, make sure the connection
136 ;; is closed. I shouldn't get this here, but if you
137 ;; tinker with the flow of this example, it is a good
139 (format t
"Client ~A:~A: connection reset by peer.~%" who port
)
140 (funcall disconnector who port
:close
))
143 ;; In this server, if the client doesn't accept data,
144 ;; it also means it will never send us data again. So
145 ;; close the connection for good.
146 (format t
"Client ~A:~A got hangup on write.~%" who port
)
147 (funcall disconnector who port
:close
)))))
151 ;; This is the actual function returned from make-ex7-io-buffer
152 ;; which allows us access to the read/writer in the scope of the
153 ;; closure. We will ask for the correct functions when setting
154 ;; up the io handlers. NOTE: By simply asking for the handler,
155 ;; I've assumed it is to be immediately put into an iolib event
156 ;; handler. This is why they are considered registered at this point.
159 ((equalp msg
:read-a-line
)
160 (setf read-handler-registered t
)
162 ((equalp msg
:write-a-line
)
163 (setf write-handler-registered t
)
166 (error "make-ex7-buffer: Please supply :read-a-line or :write-a-line~%")))))))