monocoque/zmq/dealer.rs
1//! DEALER socket implementation.
2
3use super::common::{channel_to_io_error, parse_tcp_endpoint};
4use bytes::Bytes;
5use monocoque_core::monitor::{SocketEvent, SocketEventSender, SocketMonitor, create_monitor};
6use monocoque_core::rt::TcpStream;
7use monocoque_zmtp::dealer::DealerSocket as InternalDealer;
8use std::io;
9
10/// A DEALER socket for asynchronous request-reply patterns.
11///
12/// DEALER sockets are fair-queuing clients that distribute messages
13/// across multiple server endpoints. They're used for:
14///
15/// - Load-balanced request-reply
16/// - Async RPC clients
17/// - Worker pools
18///
19/// ## ZeroMQ Compatibility
20///
21/// Compatible with `zmq::DEALER` and `zmq::ROUTER` sockets from libzmq.
22///
23/// ## Example
24///
25/// ```rust,no_run
26/// use monocoque::zmq::DealerSocket;
27/// use bytes::Bytes;
28///
29/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
30/// // Connect to server
31/// let mut socket = DealerSocket::connect("127.0.0.1:5555").await?;
32///
33/// // Send request
34/// socket.send(vec![Bytes::from("REQUEST")]).await?;
35///
36/// // Receive reply
37/// if let Ok(Some(reply)) = socket.recv().await {
38/// println!("Got reply: {:?}", reply);
39/// }
40/// # Ok(())
41/// # }
42/// ```
43pub struct DealerSocket<S = TcpStream>
44where
45 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
46{
47 inner: InternalDealer<S>,
48 monitor: Option<SocketEventSender>,
49}
50
51impl DealerSocket {
52 /// Connect to a ZeroMQ peer and create a DEALER socket.
53 ///
54 /// Supports both TCP and IPC endpoints:
55 /// - TCP: `"tcp://127.0.0.1:5555"` or `"127.0.0.1:5555"`
56 /// - IPC: `"ipc:///tmp/socket.sock"` (Unix only)
57 ///
58 /// # Arguments
59 ///
60 /// * `endpoint` - Endpoint to connect to
61 ///
62 /// # Errors
63 ///
64 /// Returns an error if:
65 /// - The connection fails (network unreachable, connection refused, etc.)
66 /// - DNS resolution fails for TCP endpoints
67 /// - Invalid endpoint format
68 ///
69 /// # Example
70 ///
71 /// ```rust,no_run
72 /// use monocoque::zmq::DealerSocket;
73 ///
74 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
75 /// // TCP connection
76 /// let socket1 = DealerSocket::connect("tcp://127.0.0.1:5555").await?;
77 ///
78 /// // IPC connection (Unix only)
79 /// #[cfg(unix)]
80 /// let socket2 = DealerSocket::connect("ipc:///tmp/socket.sock").await?;
81 /// # Ok(())
82 /// # }
83 /// ```
84 pub async fn connect(endpoint: &str) -> io::Result<Self> {
85 let addr = parse_tcp_endpoint(endpoint)?;
86 let inner = InternalDealer::connect_with_options(
87 addr,
88 monocoque_core::options::SocketOptions::default(),
89 )
90 .await?;
91 let sock = Self {
92 inner,
93 monitor: None,
94 };
95 sock.emit_event(SocketEvent::Connected(
96 monocoque_core::endpoint::Endpoint::Tcp(addr),
97 ));
98 Ok(sock)
99 }
100
101 /// Connect to a ZeroMQ peer with custom socket options.
102 ///
103 /// Stores the endpoint so the socket can reconnect automatically after failures.
104 ///
105 /// # Example
106 ///
107 /// ```rust,no_run
108 /// use monocoque::zmq::{DealerSocket, SocketOptions};
109 /// use std::time::Duration;
110 ///
111 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
112 /// let mut socket = DealerSocket::connect_with_options(
113 /// "tcp://127.0.0.1:5555",
114 /// SocketOptions::default().with_send_hwm(100),
115 /// ).await?;
116 /// # Ok(())
117 /// # }
118 /// ```
119 pub async fn connect_with_options(
120 endpoint: &str,
121 options: monocoque_core::options::SocketOptions,
122 ) -> io::Result<Self> {
123 let addr = parse_tcp_endpoint(endpoint)?;
124 let inner = InternalDealer::connect_with_options(addr, options).await?;
125 let sock = Self {
126 inner,
127 monitor: None,
128 };
129 sock.emit_event(SocketEvent::Connected(
130 monocoque_core::endpoint::Endpoint::Tcp(addr),
131 ));
132 Ok(sock)
133 }
134
135 /// Connect to a ZeroMQ peer via IPC (Unix domain sockets).
136 ///
137 /// Unix-only. Accepts IPC paths with or without `ipc://` prefix.
138 ///
139 /// # Example
140 ///
141 /// ```rust,no_run
142 /// # #[cfg(unix)]
143 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
144 /// use monocoque::zmq::DealerSocket;
145 ///
146 /// let mut socket = DealerSocket::connect_ipc("/tmp/dealer.sock").await?;
147 /// # Ok(())
148 /// # }
149 /// ```
150 #[cfg(unix)]
151 pub async fn connect_ipc(
152 path: &str,
153 ) -> io::Result<DealerSocket<monocoque_core::rt::UnixStream>> {
154 use std::path::PathBuf;
155
156 let clean_path = path.strip_prefix("ipc://").unwrap_or(path);
157 let ipc_path = PathBuf::from(clean_path);
158
159 let stream = monocoque_core::ipc::connect(&ipc_path).await?;
160 let sock = DealerSocket::from_unix_stream(stream).await?;
161 sock.emit_event(SocketEvent::Connected(
162 monocoque_core::endpoint::Endpoint::Ipc(ipc_path),
163 ));
164 Ok(sock)
165 }
166
167 /// Bind to an address and accept the first connection.
168 ///
169 /// This creates a server-side DEALER socket that accepts incoming connections.
170 /// Useful for broker patterns where workers (REP sockets) connect to a DEALER backend.
171 ///
172 /// # Returns
173 ///
174 /// A tuple of `(listener, socket)` where:
175 /// - `listener` can be used to accept additional connections
176 /// - `socket` is ready to send/receive with the first peer
177 ///
178 /// # Errors
179 ///
180 /// Returns an error if:
181 /// - The address is already in use
182 /// - Permission denied (e.g., binding to privileged port without root)
183 /// - Invalid address format
184 ///
185 /// # Example
186 ///
187 /// ```rust,no_run
188 /// use monocoque::zmq::DealerSocket;
189 ///
190 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
191 /// // Bind DEALER backend for worker connections
192 /// let (listener, socket) = DealerSocket::bind("127.0.0.1:5556").await?;
193 ///
194 /// // Use socket for first connection
195 /// // Accept more connections from listener if needed:
196 /// // let (stream, _) = listener.accept().await?;
197 /// // let socket2 = DealerSocket::from_tcp(stream).await?;
198 /// # Ok(())
199 /// # }
200 /// ```
201 pub async fn bind(
202 addr: impl monocoque_core::rt::ToSocketAddrs,
203 ) -> io::Result<(monocoque_core::rt::TcpListener, Self)> {
204 let listener = monocoque_core::rt::TcpListener::bind(addr).await?;
205 let (stream, _) = listener.accept().await?;
206 let socket = Self::from_tcp(stream).await?;
207 Ok((listener, socket))
208 }
209
210 /// Create a DEALER socket from a TCP stream with TCP_NODELAY enabled.
211 ///
212 /// This method automatically enables TCP_NODELAY for optimal performance,
213 /// preventing Nagle's algorithm from buffering small packets.
214 ///
215 /// Uses default buffer sizes (8KB) and socket options. For custom configuration,
216 /// use `with_options()`.
217 ///
218 /// # Example
219 ///
220 /// ```rust,no_run
221 /// use monocoque::zmq::DealerSocket;
222 /// use monocoque_core::rt::TcpStream;
223 ///
224 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
225 /// let stream = TcpStream::connect("127.0.0.1:5555").await?;
226 /// let socket = DealerSocket::from_tcp(stream).await?;
227 /// # Ok(())
228 /// # }
229 /// ```
230 pub async fn from_tcp(stream: TcpStream) -> io::Result<Self> {
231 Ok(Self {
232 inner: InternalDealer::from_tcp(stream).await?,
233 monitor: None,
234 })
235 }
236
237 /// Create a DEALER socket from a TCP stream with custom options.
238 ///
239 /// Provides full control over buffer sizes, HWM, timeouts, etc. through SocketOptions.
240 ///
241 /// # Examples
242 ///
243 /// ```rust,no_run
244 /// use monocoque::zmq::{DealerSocket, SocketOptions};
245 /// use monocoque_core::rt::TcpStream;
246 ///
247 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
248 /// let stream = TcpStream::connect("127.0.0.1:5555").await?;
249 ///
250 /// // Customize HWM only (uses default 8KB buffers)
251 /// let socket = DealerSocket::from_tcp_with_options(
252 /// stream,
253 /// SocketOptions::default().with_send_hwm(100)
254 /// ).await?;
255 /// # Ok(())
256 /// # }
257 /// ```
258 ///
259 /// ```rust,no_run
260 /// # use monocoque::zmq::{DealerSocket, SocketOptions};
261 /// # use monocoque_core::rt::TcpStream;
262 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
263 /// # let stream = TcpStream::connect("127.0.0.1:5555").await?;
264 /// // Customize both buffers and HWM
265 /// let socket = DealerSocket::from_tcp_with_options(
266 /// stream,
267 /// SocketOptions::default()
268 /// .with_buffer_sizes(4096, 4096) // 4KB buffers for low latency
269 /// .with_send_hwm(100)
270 /// ).await?;
271 /// # Ok(())
272 /// # }
273 /// ```
274 pub async fn from_tcp_with_options(
275 stream: TcpStream,
276 options: monocoque_core::options::SocketOptions,
277 ) -> io::Result<Self> {
278 Ok(Self {
279 inner: InternalDealer::from_tcp_with_options(stream, options).await?,
280 monitor: None,
281 })
282 }
283
284 /// Create a DEALER socket from any stream with custom options.
285 ///
286 /// This is the most flexible constructor - works with TCP, Unix, or in-memory streams.
287 /// Useful for testing with duplex streams.
288 ///
289 /// # Example
290 ///
291 /// ```rust,no_run
292 /// use monocoque::zmq::{DealerSocket, SocketOptions};
293 /// use monocoque_core::rt::TcpStream;
294 ///
295 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
296 /// let stream = TcpStream::connect("127.0.0.1:5555").await?;
297 /// let socket = DealerSocket::with_options(
298 /// stream,
299 /// SocketOptions::default().with_send_hwm(10)
300 /// ).await?;
301 /// # Ok(())
302 /// # }
303 /// ```
304 pub async fn with_options<Stream>(
305 stream: Stream,
306 options: monocoque_core::options::SocketOptions,
307 ) -> io::Result<DealerSocket<Stream>>
308 where
309 Stream: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
310 {
311 Ok(DealerSocket {
312 inner: InternalDealer::with_options(stream, options).await?,
313 monitor: None,
314 })
315 }
316}
317
318// Generic impl - works with any stream type
319impl<S> DealerSocket<S>
320where
321 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
322{
323 /// Enable monitoring for this socket.
324 ///
325 /// Returns a receiver for socket lifecycle events. Once enabled, the socket
326 /// will emit events like Connected, Disconnected, etc.
327 ///
328 /// # Example
329 ///
330 /// ```rust,no_run
331 /// use monocoque::zmq::{DealerSocket, SocketEvent};
332 ///
333 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
334 /// let mut socket = DealerSocket::connect("127.0.0.1:5555").await?;
335 /// let monitor = socket.monitor();
336 ///
337 /// // Spawn task to handle events
338 /// monocoque::rt::spawn_detached(async move {
339 /// while let Ok(event) = monitor.recv_async().await {
340 /// println!("Socket event: {}", event);
341 /// }
342 /// });
343 /// # Ok(())
344 /// # }
345 /// ```
346 pub fn monitor(&mut self) -> SocketMonitor {
347 let (sender, receiver) = create_monitor();
348 self.monitor = Some(sender);
349 receiver
350 }
351
352 /// Helper to emit monitoring events (if monitoring is enabled).
353 fn emit_event(&self, event: SocketEvent) {
354 if let Some(monitor) = &self.monitor {
355 monocoque_core::monitor::emit(monitor, event);
356 }
357 }
358
359 /// Send a multipart message.
360 ///
361 /// Messages are sent asynchronously - this returns immediately after
362 /// queuing the message for transmission.
363 ///
364 /// # Errors
365 ///
366 /// Returns an error if the underlying connection is closed or broken.
367 ///
368 /// # Example
369 ///
370 /// ```rust,no_run
371 /// # use monocoque::zmq::DealerSocket;
372 /// # use bytes::Bytes;
373 /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
374 /// socket.send(vec![
375 /// Bytes::from("part1"),
376 /// Bytes::from("part2"),
377 /// ]).await?;
378 /// # Ok(())
379 /// # }
380 /// ```
381 pub async fn send(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
382 channel_to_io_error(self.inner.send(msg).await)
383 }
384
385 /// Send a message to the internal buffer without flushing.
386 ///
387 /// Use this for batching multiple messages before a single flush.
388 /// Call `flush()` to send all buffered messages.
389 ///
390 /// # Example
391 ///
392 /// ```rust,no_run
393 /// # use monocoque::zmq::DealerSocket;
394 /// # use bytes::Bytes;
395 /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
396 /// // Batch 100 messages
397 /// for i in 0..100 {
398 /// socket.send_buffered(vec![Bytes::from(format!("msg {}", i))])?;
399 /// }
400 /// // Single I/O operation for all 100 messages
401 /// socket.flush().await?;
402 /// # Ok(())
403 /// # }
404 /// ```
405 pub fn send_buffered(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
406 channel_to_io_error(self.inner.send_buffered(msg))
407 }
408
409 /// Flush all buffered messages to the network.
410 ///
411 /// Sends all messages buffered by `send_buffered()` in a single I/O operation.
412 pub async fn flush(&mut self) -> io::Result<()> {
413 channel_to_io_error(self.inner.flush().await)
414 }
415
416 /// Send multiple messages in a single batch (convenience method).
417 ///
418 /// This is equivalent to calling `send_buffered()` for each message
419 /// followed by `flush()`, but more ergonomic.
420 ///
421 /// # Example
422 ///
423 /// ```rust,no_run
424 /// # use monocoque::zmq::DealerSocket;
425 /// # use bytes::Bytes;
426 /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
427 /// let messages = vec![
428 /// vec![Bytes::from("msg1")],
429 /// vec![Bytes::from("msg2")],
430 /// vec![Bytes::from("msg3")],
431 /// ];
432 /// socket.send_batch(&messages).await?;
433 /// # Ok(())
434 /// # }
435 /// ```
436 pub async fn send_batch(&mut self, messages: &[Vec<Bytes>]) -> io::Result<()> {
437 channel_to_io_error(self.inner.send_batch(messages).await)
438 }
439
440 /// Get the number of bytes currently buffered.
441 #[inline]
442 pub fn buffered_bytes(&self) -> usize {
443 self.inner.buffered_bytes()
444 }
445
446 /// Get the socket type.
447 ///
448 /// # ZeroMQ Compatibility
449 /// Get current socket events (read/write readiness).
450 ///
451 /// Returns a bitmask:
452 /// - `1` (POLLIN): Can receive without blocking
453 /// - `2` (POLLOUT): Can send without blocking
454 ///
455 /// # ZeroMQ Compatibility
456 ///
457 /// Corresponds to `ZMQ_EVENTS` (15) option.
458 pub fn events(&self) -> u32 {
459 self.inner.events()
460 }
461
462 /// Receive a multipart message.
463 ///
464 /// Returns `None` if the connection is closed.
465 ///
466 /// # Example
467 ///
468 /// ```rust,no_run
469 /// # use monocoque::zmq::DealerSocket;
470 /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
471 /// while let Ok(Some(msg)) = socket.recv().await {
472 /// println!("Received {} parts", msg.len());
473 /// }
474 /// # Ok(())
475 /// # }
476 /// ```
477 pub async fn recv(&mut self) -> io::Result<Option<Vec<Bytes>>> {
478 self.inner.recv().await
479 }
480}
481
482impl<S> DealerSocket<S>
483where
484 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
485{
486 /// Get the socket type.
487 ///
488 /// Always returns `SocketType::Dealer` for DEALER sockets.
489 ///
490 /// # ZeroMQ Compatibility
491 ///
492 /// Corresponds to `ZMQ_TYPE` (16) socket option.
493 #[inline]
494 pub const fn socket_type(&self) -> monocoque_zmtp::session::SocketType {
495 self.inner.socket_type()
496 }
497
498 /// Get the last connected endpoint as a string.
499 ///
500 /// Returns the endpoint this socket connected to, if any.
501 ///
502 /// # ZeroMQ Compatibility
503 ///
504 /// Corresponds to `ZMQ_LAST_ENDPOINT` (32) socket option.
505 #[inline]
506 pub fn last_endpoint(&self) -> Option<&str> {
507 self.inner.last_endpoint_string()
508 }
509
510 /// Check if more message frames are expected.
511 ///
512 /// For multipart messages, this indicates if more frames follow.
513 ///
514 /// # ZeroMQ Compatibility
515 ///
516 /// Corresponds to `ZMQ_RCVMORE` (13) socket option.
517 #[inline]
518 pub fn has_more(&self) -> bool {
519 self.inner.has_more()
520 }
521
522 /// Get mutable access to socket options.
523 ///
524 /// Allows runtime modification of socket behavior.
525 #[inline]
526 pub fn options_mut(&mut self) -> &mut monocoque_core::options::SocketOptions {
527 self.inner.options_mut()
528 }
529
530 /// Get immutable access to socket options.
531 #[inline]
532 pub const fn options(&self) -> &monocoque_core::options::SocketOptions {
533 self.inner.options()
534 }
535}
536
537// Unix-specific impl for IPC support
538#[cfg(unix)]
539impl DealerSocket<monocoque_core::rt::UnixStream> {
540 /// Create a DEALER socket from an existing Unix domain socket stream (IPC).
541 pub async fn from_unix_stream(stream: monocoque_core::rt::UnixStream) -> io::Result<Self> {
542 Ok(Self {
543 inner: InternalDealer::new(stream).await?,
544 monitor: None,
545 })
546 }
547
548 /// Create a DEALER socket from an existing Unix stream with custom options.
549 pub async fn from_unix_stream_with_options(
550 stream: monocoque_core::rt::UnixStream,
551 options: monocoque_core::options::SocketOptions,
552 ) -> io::Result<Self> {
553 Ok(Self {
554 inner: InternalDealer::with_options(stream, options).await?,
555 monitor: None,
556 })
557 }
558}
559
560// Implement ProxySocket for the high-level DealerSocket wrapper
561impl monocoque_zmtp::proxy::ProxySocket for DealerSocket<TcpStream> {
562 fn recv_multipart<'life0, 'async_trait>(
563 &'life0 mut self,
564 ) -> ::core::pin::Pin<
565 Box<dyn ::core::future::Future<Output = io::Result<Option<Vec<Bytes>>>> + 'async_trait>,
566 >
567 where
568 'life0: 'async_trait,
569 Self: 'async_trait,
570 {
571 Box::pin(async move { self.recv().await })
572 }
573
574 fn send_multipart<'life0, 'async_trait>(
575 &'life0 mut self,
576 msg: Vec<Bytes>,
577 ) -> ::core::pin::Pin<Box<dyn ::core::future::Future<Output = io::Result<()>> + 'async_trait>>
578 where
579 'life0: 'async_trait,
580 Self: 'async_trait,
581 {
582 Box::pin(async move { self.send(msg).await })
583 }
584
585 fn socket_desc(&self) -> &'static str {
586 "DEALER"
587 }
588}