PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/ffi-rzmq/socket.rb

http://github.com/chuckremes/ffi-rzmq
Ruby | 611 lines | 236 code | 71 blank | 304 comment | 40 complexity | f5e7a7c1f7d2324e9daf667501a7385a MD5 | raw file
  1. module ZMQ
  2. class Socket
  3. attr_reader :socket, :name
  4. # Allocates a socket of type +type+ for sending and receiving data.
  5. #
  6. # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
  7. # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP,
  8. # ZMQ::DEALER or ZMQ::ROUTER.
  9. #
  10. # By default, this class uses ZMQ::Message for manual
  11. # memory management. For automatic garbage collection of received messages,
  12. # it is possible to override the :receiver_class to use ZMQ::ManagedMessage.
  13. #
  14. # sock = Socket.create(Context.create, ZMQ::REQ, :receiver_class => ZMQ::ManagedMessage)
  15. #
  16. # Advanced users may want to replace the receiver class with their
  17. # own custom class. The custom class must conform to the same public API
  18. # as ZMQ::Message.
  19. #
  20. # Creation of a new Socket object can return nil when socket creation
  21. # fails.
  22. #
  23. # if (socket = Socket.new(context.pointer, ZMQ::REQ))
  24. # ...
  25. # else
  26. # STDERR.puts "Socket creation failed"
  27. # end
  28. #
  29. def self.create context_ptr, type, opts = {:receiver_class => ZMQ::Message}
  30. new(context_ptr, type, opts) rescue nil
  31. end
  32. # To avoid rescuing exceptions, use the factory method #create for
  33. # all socket creation.
  34. #
  35. # Allocates a socket of type +type+ for sending and receiving data.
  36. #
  37. # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
  38. # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP,
  39. # ZMQ::DEALER or ZMQ::ROUTER.
  40. #
  41. # By default, this class uses ZMQ::Message for manual
  42. # memory management. For automatic garbage collection of received messages,
  43. # it is possible to override the :receiver_class to use ZMQ::ManagedMessage.
  44. #
  45. # sock = Socket.new(Context.new, ZMQ::REQ, :receiver_class => ZMQ::ManagedMessage)
  46. #
  47. # Advanced users may want to replace the receiver class with their
  48. # own custom class. The custom class must conform to the same public API
  49. # as ZMQ::Message.
  50. #
  51. # Creation of a new Socket object can raise an exception. This occurs when the
  52. # +context_ptr+ is null or when the allocation of the 0mq socket within the
  53. # context fails.
  54. #
  55. # begin
  56. # socket = Socket.new(context.pointer, ZMQ::REQ)
  57. # rescue ContextError => e
  58. # # error handling
  59. # end
  60. #
  61. def initialize context_ptr, type, opts = {:receiver_class => ZMQ::Message}
  62. # users may override the classes used for receiving; class must conform to the
  63. # same public API as ZMQ::Message
  64. @receiver_klass = opts[:receiver_class]
  65. context_ptr = context_ptr.pointer if context_ptr.kind_of?(ZMQ::Context)
  66. if context_ptr.nil? || context_ptr.null?
  67. raise ContextError.new 'zmq_socket', 0, ETERM, "Context pointer was null"
  68. else
  69. @socket = LibZMQ.zmq_socket context_ptr, type
  70. if @socket && !@socket.null?
  71. @name = SocketTypeNameMap[type]
  72. else
  73. raise ContextError.new 'zmq_socket', 0, ETERM, "Socket pointer was null"
  74. end
  75. end
  76. @longlong_cache = @int_cache = nil
  77. @more_parts_array = []
  78. @option_lookup = []
  79. populate_option_lookup
  80. define_finalizer
  81. end
  82. # Set the queue options on this socket.
  83. #
  84. # Valid +name+ values that take a numeric +value+ are:
  85. # ZMQ::HWM
  86. # ZMQ::SWAP (version 2 only)
  87. # ZMQ::AFFINITY
  88. # ZMQ::RATE
  89. # ZMQ::RECOVERY_IVL
  90. # ZMQ::MCAST_LOOP (version 2 only)
  91. # ZMQ::LINGER
  92. # ZMQ::RECONNECT_IVL
  93. # ZMQ::BACKLOG
  94. # ZMQ::RECOVER_IVL_MSEC (version 2 only)
  95. # ZMQ::RECONNECT_IVL_MAX (version 3 only)
  96. # ZMQ::MAXMSGSIZE (version 3 only)
  97. # ZMQ::SNDHWM (version 3 only)
  98. # ZMQ::RCVHWM (version 3 only)
  99. # ZMQ::MULTICAST_HOPS (version 3 only)
  100. # ZMQ::RCVTIMEO (version 3 only)
  101. # ZMQ::SNDTIMEO (version 3 only)
  102. #
  103. # Valid +name+ values that take a string +value+ are:
  104. # ZMQ::IDENTITY (version 2/3 only)
  105. # ZMQ::SUBSCRIBE
  106. # ZMQ::UNSUBSCRIBE
  107. #
  108. # Returns 0 when the operation completed successfully.
  109. # Returns -1 when this operation failed.
  110. #
  111. # With a -1 return code, the user must check ZMQ::Util.errno to determine the
  112. # cause.
  113. #
  114. # rc = socket.setsockopt(ZMQ::LINGER, 1_000)
  115. # ZMQ::Util.resultcode_ok?(rc) ? puts("succeeded") : puts("failed")
  116. #
  117. def setsockopt name, value, length = nil
  118. if 1 == @option_lookup[name]
  119. length = 8
  120. pointer = LibC.malloc length
  121. pointer.write_long_long value
  122. elsif 0 == @option_lookup[name]
  123. length = 4
  124. pointer = LibC.malloc length
  125. pointer.write_int value
  126. elsif 2 == @option_lookup[name]
  127. # Strings are treated as pointers by FFI so we'll just pass it through
  128. length ||= value.size
  129. pointer = value
  130. end
  131. rc = LibZMQ.zmq_setsockopt @socket, name, pointer, length
  132. LibC.free(pointer) unless pointer.is_a?(String) || pointer.nil? || pointer.null?
  133. rc
  134. end
  135. # Convenience method for checking on additional message parts.
  136. #
  137. # Equivalent to calling Socket#getsockopt with ZMQ::RCVMORE.
  138. #
  139. # Warning: if the call to #getsockopt fails, this method will return
  140. # false and swallow the error.
  141. #
  142. # message_parts = []
  143. # message = Message.new
  144. # rc = socket.recvmsg(message)
  145. # if ZMQ::Util.resultcode_ok?(rc)
  146. # message_parts << message
  147. # while more_parts?
  148. # message = Message.new
  149. # rc = socket.recvmsg(message)
  150. # message_parts.push(message) if resulcode_ok?(rc)
  151. # end
  152. # end
  153. #
  154. def more_parts?
  155. rc = getsockopt ZMQ::RCVMORE, @more_parts_array
  156. Util.resultcode_ok?(rc) ? @more_parts_array.at(0) : false
  157. end
  158. # Binds the socket to an +address+.
  159. #
  160. # socket.bind("tcp://127.0.0.1:5555")
  161. #
  162. def bind address
  163. LibZMQ.zmq_bind @socket, address
  164. end
  165. # Connects the socket to an +address+.
  166. #
  167. # rc = socket.connect("tcp://127.0.0.1:5555")
  168. #
  169. def connect address
  170. rc = LibZMQ.zmq_connect @socket, address
  171. end
  172. # Closes the socket. Any unprocessed messages in queue are sent or dropped
  173. # depending upon the value of the socket option ZMQ::LINGER.
  174. #
  175. # Returns 0 upon success *or* when the socket has already been closed.
  176. # Returns -1 when the operation fails. Check ZMQ::Util.errno for the error code.
  177. #
  178. # rc = socket.close
  179. # puts("Given socket was invalid!") unless 0 == rc
  180. #
  181. def close
  182. if @socket
  183. remove_finalizer
  184. rc = LibZMQ.zmq_close @socket
  185. @socket = nil
  186. release_cache
  187. rc
  188. else
  189. 0
  190. end
  191. end
  192. # Queues the message for transmission. Message is assumed to conform to the
  193. # same public API as #Message.
  194. #
  195. # +flags+ may take two values:
  196. # * 0 (default) - blocking operation
  197. # * ZMQ::DONTWAIT - non-blocking operation
  198. # * ZMQ::SNDMORE - this message is part of a multi-part message
  199. #
  200. # Returns 0 when the message was successfully enqueued.
  201. # Returns -1 under two conditions.
  202. # 1. The message could not be enqueued
  203. # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
  204. #
  205. # With a -1 return code, the user must check ZMQ::Util.errno to determine the
  206. # cause.
  207. #
  208. def sendmsg message, flags = 0
  209. __sendmsg__(@socket, message.address, flags)
  210. end
  211. # Helper method to make a new #Message instance out of the +string+ passed
  212. # in for transmission.
  213. #
  214. # +flags+ may be ZMQ::DONTWAIT and ZMQ::SNDMORE.
  215. #
  216. # Returns 0 when the message was successfully enqueued.
  217. # Returns -1 under two conditions.
  218. # 1. The message could not be enqueued
  219. # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
  220. #
  221. # With a -1 return code, the user must check ZMQ::Util.errno to determine the
  222. # cause.
  223. #
  224. def send_string string, flags = 0
  225. message = Message.new string
  226. send_and_close message, flags
  227. end
  228. # Send a sequence of strings as a multipart message out of the +parts+
  229. # passed in for transmission. Every element of +parts+ should be
  230. # a String.
  231. #
  232. # +flags+ may be ZMQ::DONTWAIT.
  233. #
  234. # Returns 0 when the messages were successfully enqueued.
  235. # Returns -1 under two conditions.
  236. # 1. A message could not be enqueued
  237. # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
  238. #
  239. # With a -1 return code, the user must check ZMQ::Util.errno to determine the
  240. # cause.
  241. #
  242. def send_strings parts, flags = 0
  243. send_multiple(parts, flags, :send_string)
  244. end
  245. # Send a sequence of messages as a multipart message out of the +parts+
  246. # passed in for transmission. Every element of +parts+ should be
  247. # a Message (or subclass).
  248. #
  249. # +flags+ may be ZMQ::DONTWAIT.
  250. #
  251. # Returns 0 when the messages were successfully enqueued.
  252. # Returns -1 under two conditions.
  253. # 1. A message could not be enqueued
  254. # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
  255. #
  256. # With a -1 return code, the user must check ZMQ::Util.errno to determine the
  257. # cause.
  258. #
  259. def sendmsgs parts, flags = 0
  260. send_multiple(parts, flags, :sendmsg)
  261. end
  262. # Sends a message. This will automatically close the +message+ for both successful
  263. # and failed sends.
  264. #
  265. # Returns 0 when the message was successfully enqueued.
  266. # Returns -1 under two conditions.
  267. # 1. The message could not be enqueued
  268. # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
  269. #
  270. # With a -1 return code, the user must check ZMQ::Util.errno to determine the
  271. # cause.
  272. #
  273. def send_and_close message, flags = 0
  274. rc = sendmsg message, flags
  275. message.close
  276. rc
  277. end
  278. # Dequeues a message from the underlying queue. By default, this is a blocking operation.
  279. #
  280. # +flags+ may take two values:
  281. # 0 (default) - blocking operation
  282. # ZMQ::DONTWAIT - non-blocking operation
  283. #
  284. # Returns 0 when the message was successfully dequeued.
  285. # Returns -1 under two conditions.
  286. # 1. The message could not be dequeued
  287. # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
  288. #
  289. # With a -1 return code, the user must check ZMQ::Util.errno to determine the
  290. # cause.
  291. #
  292. # The application code is responsible for handling the +message+ object lifecycle
  293. # when #recv returns an error code.
  294. #
  295. def recvmsg message, flags = 0
  296. #LibZMQ.zmq_recvmsg @socket, message.address, flags
  297. __recvmsg__(@socket, message.address, flags)
  298. end
  299. # Helper method to make a new #Message instance and convert its payload
  300. # to a string.
  301. #
  302. # +flags+ may be ZMQ::DONTWAIT.
  303. #
  304. # Returns 0 when the message was successfully dequeued.
  305. # Returns -1 under two conditions.
  306. # 1. The message could not be dequeued
  307. # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
  308. #
  309. # With a -1 return code, the user must check ZMQ::Util.errno to determine the
  310. # cause.
  311. #
  312. # The application code is responsible for handling the +message+ object lifecycle
  313. # when #recv returns an error code.
  314. #
  315. def recv_string string, flags = 0
  316. message = @receiver_klass.new
  317. rc = recvmsg message, flags
  318. string.replace(message.copy_out_string) if Util.resultcode_ok?(rc)
  319. message.close
  320. rc
  321. end
  322. # Receive a multipart message as a list of strings.
  323. #
  324. # +flag+ may be ZMQ::DONTWAIT. Any other flag will be
  325. # removed.
  326. #
  327. def recv_strings list, flag = 0
  328. array = []
  329. rc = recvmsgs array, flag
  330. if Util.resultcode_ok?(rc)
  331. array.each do |message|
  332. list << message.copy_out_string
  333. message.close
  334. end
  335. end
  336. rc
  337. end
  338. # Receive a multipart message as an array of objects
  339. # (by default these are instances of Message).
  340. #
  341. # +flag+ may be ZMQ::DONTWAIT. Any other flag will be
  342. # removed.
  343. #
  344. def recvmsgs list, flag = 0
  345. flag = DONTWAIT if dontwait?(flag)
  346. message = @receiver_klass.new
  347. rc = recvmsg message, flag
  348. if Util.resultcode_ok?(rc)
  349. list << message
  350. # check rc *first*; necessary because the call to #more_parts? can reset
  351. # the zmq_errno to a weird value, so the zmq_errno that was set on the
  352. # call to #recv gets lost
  353. while Util.resultcode_ok?(rc) && more_parts?
  354. message = @receiver_klass.new
  355. rc = recvmsg message, flag
  356. if Util.resultcode_ok?(rc)
  357. list << message
  358. else
  359. message.close
  360. list.each { |msg| msg.close }
  361. list.clear
  362. end
  363. end
  364. else
  365. message.close
  366. end
  367. rc
  368. end
  369. # Should only be used for XREQ, XREP, DEALER and ROUTER type sockets. Takes
  370. # a +list+ for receiving the message body parts and a +routing_envelope+
  371. # for receiving the message parts comprising the 0mq routing information.
  372. #
  373. def recv_multipart list, routing_envelope, flag = 0
  374. parts = []
  375. rc = recvmsgs parts, flag
  376. if Util.resultcode_ok?(rc)
  377. routing = true
  378. parts.each do |part|
  379. if routing
  380. routing_envelope << part
  381. routing = part.size > 0
  382. else
  383. list << part
  384. end
  385. end
  386. end
  387. rc
  388. end
  389. # Get the options set on this socket.
  390. #
  391. # +name+ determines the socket option to request
  392. # +array+ should be an empty array; a result of the proper type
  393. # (numeric, string, boolean) will be inserted into
  394. # the first position.
  395. #
  396. # Valid +option_name+ values:
  397. # ZMQ::RCVMORE - true or false
  398. # ZMQ::HWM - integer
  399. # ZMQ::SWAP - integer
  400. # ZMQ::AFFINITY - bitmap in an integer
  401. # ZMQ::IDENTITY - string
  402. # ZMQ::RATE - integer
  403. # ZMQ::RECOVERY_IVL - integer
  404. # ZMQ::SNDBUF - integer
  405. # ZMQ::RCVBUF - integer
  406. # ZMQ::FD - fd in an integer
  407. # ZMQ::EVENTS - bitmap integer
  408. # ZMQ::LINGER - integer measured in milliseconds
  409. # ZMQ::RECONNECT_IVL - integer measured in milliseconds
  410. # ZMQ::BACKLOG - integer
  411. # ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds
  412. # ZMQ::IPV4ONLY - integer
  413. #
  414. # Returns 0 when the operation completed successfully.
  415. # Returns -1 when this operation failed.
  416. #
  417. # With a -1 return code, the user must check ZMQ::Util.errno to determine the
  418. # cause.
  419. #
  420. # # retrieve high water mark
  421. # array = []
  422. # rc = socket.getsockopt(ZMQ::HWM, array)
  423. # hwm = array.first if ZMQ::Util.resultcode_ok?(rc)
  424. #
  425. def getsockopt name, array
  426. rc = __getsockopt__ name, array
  427. if Util.resultcode_ok?(rc) && (RCVMORE == name)
  428. # convert to boolean
  429. array[0] = 1 == array[0]
  430. end
  431. rc
  432. end
  433. # Convenience method for getting the value of the socket IDENTITY.
  434. #
  435. def identity
  436. array = []
  437. getsockopt IDENTITY, array
  438. array.at(0)
  439. end
  440. # Convenience method for setting the value of the socket IDENTITY.
  441. #
  442. def identity=(value)
  443. setsockopt IDENTITY, value.to_s
  444. end
  445. # Disconnect the socket from the given +endpoint+.
  446. #
  447. def disconnect(endpoint)
  448. LibZMQ.zmq_disconnect(socket, endpoint)
  449. end
  450. # Unbind the socket from the given +endpoint+.
  451. #
  452. def unbind(endpoint)
  453. LibZMQ.zmq_unbind(socket, endpoint)
  454. end
  455. private
  456. def send_multiple(parts, flags, method_name)
  457. if !parts || parts.empty?
  458. -1
  459. else
  460. flags = DONTWAIT if dontwait?(flags)
  461. rc = 0
  462. parts[0..-2].each do |part|
  463. rc = send(method_name, part, (flags | ZMQ::SNDMORE))
  464. break unless Util.resultcode_ok?(rc)
  465. end
  466. Util.resultcode_ok?(rc) ? send(method_name, parts[-1], flags) : rc
  467. end
  468. end
  469. def __getsockopt__ name, array
  470. # a small optimization so we only have to determine the option
  471. # type a single time; gives approx 5% speedup to do it this way.
  472. option_type = @option_lookup[name]
  473. value, length = sockopt_buffers option_type
  474. rc = LibZMQ.zmq_getsockopt @socket, name, value, length
  475. if Util.resultcode_ok?(rc)
  476. array[0] = if 1 == option_type
  477. value.read_long_long
  478. elsif 0 == option_type
  479. value.read_int
  480. elsif 2 == option_type
  481. value.read_string(length.read_int)
  482. end
  483. end
  484. rc
  485. end
  486. # Calls to ZMQ.getsockopt require us to pass in some pointers. We can cache and save those buffers
  487. # for subsequent calls. This is a big perf win for calling RCVMORE which happens quite often.
  488. # Cannot save the buffer for the IDENTITY.
  489. def sockopt_buffers option_type
  490. if 1 == option_type
  491. # int64_t or uint64_t
  492. @longlong_cache ||= alloc_pointer(:int64, 8)
  493. elsif 0 == option_type
  494. # int, 0mq assumes int is 4-bytes
  495. @int_cache ||= alloc_pointer(:int32, 4)
  496. elsif 2 == option_type
  497. # could be a string of up to 255 bytes, so allocate for worst case
  498. alloc_pointer(255, 255)
  499. else
  500. # uh oh, someone passed in an unknown option; return nil
  501. @int_cache ||= alloc_pointer(:int32, 4)
  502. end
  503. end
  504. def release_cache
  505. @longlong_cache = nil
  506. @int_cache = nil
  507. end
  508. def dontwait?(flags)
  509. (DONTWAIT & flags) == DONTWAIT
  510. end
  511. alias :noblock? :dontwait?
  512. def alloc_pointer(kind, length)
  513. pointer = FFI::MemoryPointer.new :size_t
  514. pointer.write_int(length)
  515. [FFI::MemoryPointer.new(kind), pointer]
  516. end
  517. def __sendmsg__(socket, address, flags)
  518. LibZMQ.zmq_sendmsg(socket, address, flags)
  519. end
  520. def __recvmsg__(socket, address, flags)
  521. LibZMQ.zmq_recvmsg(socket, address, flags)
  522. end
  523. def populate_option_lookup
  524. IntegerSocketOptions.each { |option| @option_lookup[option] = 0 }
  525. LongLongSocketOptions.each { |option| @option_lookup[option] = 1 }
  526. StringSocketOptions.each { |option| @option_lookup[option] = 2 }
  527. end
  528. # these finalizer-related methods cannot live in the CommonSocketBehavior
  529. # module; they *must* be in the class definition directly
  530. def define_finalizer
  531. ObjectSpace.define_finalizer(self, self.class.close(@socket, Process.pid))
  532. end
  533. def remove_finalizer
  534. ObjectSpace.undefine_finalizer self
  535. end
  536. def self.close socket, pid
  537. Proc.new { LibZMQ.zmq_close socket if Process.pid == pid }
  538. end
  539. end # Socket
  540. end # module ZMQ