PageRenderTime 61ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/examples/CL/zmsg.lisp

https://github.com/pjkundert/zguide
Lisp | 271 lines | 201 code | 41 blank | 29 comment | 6 complexity | 051dd93f0e37c32d2b8422906866ca69 MD5 | raw file
  1. ;;; -*- Mode:Lisp; Syntax:ANSI-Common-Lisp; -*-
  2. ;;;
  3. ;;; Multipart message class for example applications in Common Lisp
  4. ;;;
  5. ;;; Kamil Shakirov <kamils80@gmail.com>
  6. ;;;
  7. (defpackage #:zguide.zmsg
  8. (:nicknames #:zmsg)
  9. (:use #:cl)
  10. (:shadow #:recv #:send #:push #:pop)
  11. (:export
  12. #:message
  13. #:make-message
  14. #:recv
  15. #:send
  16. #:parts
  17. #:body
  18. #:set-body
  19. #:format-body
  20. #:push
  21. #:pop
  22. #:address
  23. #:wrap
  24. #:unwrap
  25. #:dump
  26. #:test))
  27. (in-package :zguide.zmsg)
  28. (defparameter +hex-char+ "0123456789ABCDEF")
  29. (defparameter +hex-to-bin+
  30. #(-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
  31. -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
  32. -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
  33. 0 1 2 3 4 5 6 7 8 9 -1 -1 -1 -1 -1 -1
  34. -1 10 11 12 13 14 15 -1 -1 -1 -1 -1 -1 -1 -1 -1
  35. -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
  36. -1 10 11 12 13 14 15 -1 -1 -1 -1 -1 -1 -1 -1 -1
  37. -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1))
  38. (defun uuid-encode (data)
  39. "Formats 17-byte UUID as 33-char string starting with '@'."
  40. (assert (and (= (array-rank data) 1)
  41. (= (array-total-size data) 17)
  42. (= 0 (aref data 0))))
  43. (let ((uuid (make-array 32 :element-type 'character)))
  44. (dotimes (i 16)
  45. (setf (char uuid (* i 2)) (char +hex-char+ (ash (aref data (1+ i)) -4)))
  46. (setf (char uuid (1+ (* i 2))) (char +hex-char+ (logand (aref data (1+ i)) 15))))
  47. (format nil "@~A" uuid)))
  48. (defun uuid-decode (uuid)
  49. "Decodes 33-char string starting with '@' to 17-byte UUID."
  50. (assert (and (= 33 (length uuid))
  51. (char= #\@ (char uuid 0))))
  52. (let ((data (make-array 17 :element-type '(unsigned-byte 8))))
  53. (setf (aref data 0) 0)
  54. (dotimes (i 16)
  55. (setf (aref data (1+ i))
  56. (+ (ash
  57. (aref +hex-to-bin+
  58. (logand (char-code
  59. (char uuid (1+ (* 2 i))))
  60. 127))
  61. 4)
  62. (aref +hex-to-bin+
  63. (logand (char-code
  64. (char uuid (+ (* 2 i) 2)))
  65. 127)))))
  66. data))
  67. ;; * We don't care about zero-copy performance, so messages hold copies of data.
  68. ;; * Receiving a message always calls the constructor so we don't need extra constructors.
  69. ;; * Sending a message always calls the destructor, and sets the message reference to null.
  70. ;; * Message parts (addresses and data) are always printable strings.
  71. (defstruct (message
  72. (:constructor %make-message)
  73. (:copier nil))
  74. (parts nil :type (or null list))
  75. (parts-count 0 :type fixnum))
  76. (defun make-message ()
  77. "Creates empty message."
  78. (%make-message))
  79. (defun recv (socket)
  80. "Receives message from socket.
  81. Creates a new message and returns it. Blocks on recv if socket is not ready
  82. for input."
  83. (loop :with parts = (list)
  84. :for parts-count :from 1
  85. :do (let ((msg (make-instance 'zmq:msg)))
  86. (zmq:recv socket msg)
  87. ;; We handle 0MQ UUIDs as printable strings
  88. (let ((data (zmq:msg-data-as-array msg)))
  89. (if (and (= 17 (zmq:msg-size msg))
  90. (= (aref data 0) 0))
  91. ;; Store message part as string uuid
  92. (let ((uuid (uuid-encode data)))
  93. (cl:push (cons (length uuid) uuid) parts))
  94. ;; Store this message part
  95. (cl:push (cons (zmq:msg-size msg) (zmq:msg-data-as-string msg)) parts))))
  96. :until (zerop (zmq:getsockopt socket zmq:rcvmore)) ; last message part
  97. :finally (return (%make-message :parts (nreverse parts) :parts-count parts-count))))
  98. (defun send (message socket)
  99. "Sends message to socket."
  100. ;; Unmangle 0MQ identities for writing to the socket
  101. (loop :for (size . text) :in (message-parts message)
  102. :for msg = (if (and (= 33 size)
  103. (char= #\@ (char text 0)))
  104. (make-instance 'zmq:msg :data (uuid-decode text) :size size)
  105. (make-instance 'zmq:msg :data text))
  106. :do (if (zerop (decf (message-parts-count message)))
  107. (zmq:send socket msg)
  108. (zmq:send socket msg zmq:sndmore))))
  109. (defun parts (message)
  110. "Reports size of message."
  111. (message-parts-count message))
  112. (defun body (message)
  113. "Returns message body, if any.
  114. Caller should not modify the provided data."
  115. (and (message-parts message)
  116. (cdr (nth (1- (message-parts-count message)) (message-parts message)))))
  117. (defun set-body (message body)
  118. "Sets message body as copy of provided string.
  119. If message is empty, creates a new message body."
  120. (let ((part (cons (length body) body)))
  121. (if (null (message-parts message))
  122. (progn
  123. (cl:push part (message-parts message))
  124. (incf (message-parts-count message)))
  125. (setf (nth (1- (message-parts-count message)) (message-parts message)) part)))
  126. (values))
  127. (defun format-body (message fmt &rest args)
  128. "Sets message body using format.
  129. If message is empty, creates a new message body."
  130. (set-body message (apply #'format nil fmt args)))
  131. (defun push (message part)
  132. "Pushes message part to front of message parts."
  133. (incf (message-parts-count message))
  134. (cl:push (cons (length part) part) (message-parts message))
  135. (values))
  136. (defun pop (message)
  137. "Pops message part off front of message parts."
  138. (decf (message-parts-count message))
  139. (cdr (cl:pop (message-parts message))))
  140. (defun address (message)
  141. "Returns pointer to outer message address, if any.
  142. Caller should not modify the provided data."
  143. (cdr (car (message-parts message))))
  144. (defun wrap (message address &optional delim)
  145. "Wraps message in new address envelope.
  146. If delim is not NIL, creates two-part envelope."
  147. (when delim
  148. ;; Push optional delimiter and then address
  149. (push message delim))
  150. (push message address))
  151. (defun unwrap (message)
  152. "Unwraps outer message envelope and returns address.
  153. Discards empty message part after address, if any."
  154. (prog1 (pop message)
  155. (when (zerop (length (address message)))
  156. (pop message))))
  157. (defun text-part-p (part)
  158. (loop :for char :across part
  159. :for code = (char-code char)
  160. :never (or (> 32 code) (< 127 code))))
  161. (defun dump (message)
  162. "Dumps message for debugging and tracing."
  163. ;; Dump the message as text or binary
  164. (let ((*standard-output* *error-output*))
  165. (loop :for (size . data) in (message-parts message)
  166. :do (format t "[~3,'0D] " size)
  167. (if (text-part-p data)
  168. (write-string data)
  169. (loop :for x :across data
  170. :do (format t "~2,'0X" (char-code x))))
  171. (terpri))
  172. (finish-output)))
  173. (defun test (&optional verbosep)
  174. "Runs self test of class."
  175. (format t " * zmsg: ")
  176. ;; Prepare our context and sockets
  177. (zmq:with-context (context 1)
  178. (zmq:with-socket (output context zmq:xreq)
  179. (zmq:bind output "ipc://zmsg_selftest.ipc")
  180. (zmq:with-socket (input context zmq:xrep)
  181. (zmq:connect input "ipc://zmsg_selftest.ipc")
  182. ;; Test send and receive of single-part message
  183. (let ((message (make-message)))
  184. (set-body message "Hello")
  185. (assert (string= (body message) "Hello"))
  186. (send message output)
  187. (let ((message (recv input)))
  188. (assert (= (parts message) 2))
  189. (when verbosep
  190. (dump message))
  191. (assert (string= (body message) "Hello"))))
  192. ;; Test send and receive of multi-part message
  193. (let ((message (make-message)))
  194. (set-body message "Hello")
  195. (wrap message "address1" "")
  196. (wrap message "address2")
  197. (assert (= (parts message) 4))
  198. (send message output)
  199. (let ((message (recv input)))
  200. (when verbosep
  201. (dump message))
  202. (assert (= (parts message) 5))
  203. (assert (= 33 (length (address message))))
  204. (unwrap message)
  205. (assert (string= (address message) "address2"))
  206. (format-body message "~C~A" #\W "orld")
  207. (send message output))
  208. (let ((message (recv input)))
  209. (unwrap message)
  210. (assert (= (parts message) 4))
  211. (string= (body message) "World")
  212. (let ((part (unwrap message)))
  213. (assert (string= part "address2")))
  214. ;; Pull off address 1, check that empty part was dropped
  215. (let ((part (unwrap message)))
  216. (assert (string= part "address1"))
  217. (assert (= 1 (parts message))))
  218. ;; Check that message body was correctly modified
  219. (let ((part (pop message)))
  220. (assert (string= part "World"))
  221. (assert (zerop (parts message)))))
  222. (format t "OK~%"))))))
  223. ;; [033] @02E39A16025D42E68BEA84F25D6A9A0A
  224. ;; [005] Hello
  225. ;; [033] @02E39A16025D42E68BEA84F25D6A9A0A
  226. ;; [008] address2
  227. ;; [008] address1
  228. ;; [000]
  229. ;; [005] Hello
  230. ;; * zmsg: OK