diff --git a/Cargo.toml b/Cargo.toml index af87214..4ee2d75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ features = [ [dev-dependencies] easy-parallel = "3.1.0" fastrand = "2.0.0" +socket2 = "0.5.5" [target.'cfg(unix)'.dev-dependencies] libc = "0.2" diff --git a/examples/tcp_client.rs b/examples/tcp_client.rs new file mode 100644 index 0000000..413f6b6 --- /dev/null +++ b/examples/tcp_client.rs @@ -0,0 +1,36 @@ +use std::{io, net}; + +use polling::Event; +use socket2::Type; + +fn main() -> io::Result<()> { + let socket = socket2::Socket::new(socket2::Domain::IPV4, Type::STREAM, None)?; + let poller = polling::Poller::new()?; + unsafe { + poller.add(&socket, Event::new(0, true, true))?; + } + let addr = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 8080); + socket.set_nonblocking(true)?; + let _ = socket.connect(&addr.into()); + + let mut events = polling::Events::new(); + + events.clear(); + poller.wait(&mut events, None)?; + + let event = events.iter().next(); + let event = match event { + Some(event) => event, + None => { + println!("no event"); + return Ok(()); + } + }; + + println!("event: {:?}", event); + if event.is_connect_failed().unwrap_or_default() { + println!("connect failed"); + } + + Ok(()) +} diff --git a/src/epoll.rs b/src/epoll.rs index 606dd1c..6edcf60 100644 --- a/src/epoll.rs +++ b/src/epoll.rs @@ -365,6 +365,14 @@ impl EventExtra { pub fn is_pri(&self) -> bool { self.flags.contains(epoll::EventFlags::PRI) } + + #[inline] + pub fn is_connect_failed(&self) -> Option { + Some( + self.flags.contains(epoll::EventFlags::ERR) + || self.flags.contains(epoll::EventFlags::HUP), + ) + } } /// The notifier for Linux. diff --git a/src/iocp/mod.rs b/src/iocp/mod.rs index 5168e76..ce56ac4 100644 --- a/src/iocp/mod.rs +++ b/src/iocp/mod.rs @@ -681,6 +681,12 @@ impl EventExtra { pub fn set_pri(&mut self, active: bool) { self.flags.set(AfdPollMask::RECEIVE_EXPEDITED, active); } + + /// Check if TCP connect failed. + #[inline] + pub fn is_connect_failed(&self) -> Option { + Some(self.flags.intersects(AfdPollMask::CONNECT_FAIL)) + } } /// A packet used to wake up the poller with an event. diff --git a/src/kqueue.rs b/src/kqueue.rs index 359305b..3e0b044 100644 --- a/src/kqueue.rs +++ b/src/kqueue.rs @@ -371,6 +371,11 @@ impl EventExtra { pub fn is_pri(&self) -> bool { false } + + #[inline] + pub fn is_connect_failed(&self) -> Option { + None + } } pub(crate) fn mode_to_flags(mode: PollMode) -> kqueue::EventFlags { diff --git a/src/lib.rs b/src/lib.rs index 4caf19a..9c828a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,6 +332,65 @@ impl Event { self.extra.is_pri() } + /// Tells if this event is the result of a connection failure. + /// + /// This function checks if a TCP connection has failed. It corresponds to the `EPOLLERR` or `EPOLLHUP` event in Linux + /// and `CONNECT_FAILED` event in Windows IOCP. + /// + /// # Examples + /// + /// ``` + /// use std::{io, net}; + /// // Assuming polling and socket2 are included as dependencies in Cargo.toml + /// use polling::Event; + /// use socket2::Type; + /// + /// fn main() -> io::Result<()> { + /// let socket = socket2::Socket::new(socket2::Domain::IPV4, Type::STREAM, None)?; + /// let poller = polling::Poller::new()?; + /// unsafe { + /// poller.add(&socket, Event::new(0, true, true))?; + /// } + /// let addr = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 8080); + /// socket.set_nonblocking(true)?; + /// let _ = socket.connect(&addr.into()); + /// + /// let mut events = polling::Events::new(); + /// + /// events.clear(); + /// poller.wait(&mut events, None)?; + /// + /// let event = events.iter().next(); + /// + /// let event = match event { + /// Some(event) => event, + /// None => { + /// println!("no event"); + /// return Ok(()); + /// }, + /// }; + /// + /// println!("event: {:?}", event); + /// if event + /// .is_connect_failed() + /// .unwrap_or_default() + /// { + /// println!("connect failed"); + /// } + /// + /// Ok(()) + /// } + /// ``` + /// + /// # Returns + /// + /// Returns `Some(true)` if the connection has failed, `Some(false)` if the connection has not failed, + /// or `None` if the platform does not support detecting this condition. + #[inline] + pub fn is_connect_failed(&self) -> Option { + self.extra.is_connect_failed() + } + /// Remove any extra information from this event. #[inline] pub fn clear_extra(&mut self) { diff --git a/src/poll.rs b/src/poll.rs index 2e34d76..b03e410 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -426,6 +426,11 @@ impl EventExtra { pub fn is_pri(&self) -> bool { self.flags.contains(PollFlags::PRI) } + + #[inline] + pub fn is_connect_failed(&self) -> Option { + Some(self.flags.contains(PollFlags::ERR) || self.flags.contains(PollFlags::HUP)) + } } fn cvt_mode_as_remove(mode: PollMode) -> io::Result { diff --git a/src/port.rs b/src/port.rs index 9d843d3..c88b36b 100644 --- a/src/port.rs +++ b/src/port.rs @@ -250,4 +250,9 @@ impl EventExtra { pub fn is_pri(&self) -> bool { self.flags.contains(PollFlags::PRI) } + + #[inline] + pub fn is_connect_failed(&self) -> Option { + Some(self.flags.contains(PollFlags::ERR) || self.flags.contains(PollFlags::HUP)) + } }