connect()
, send()
and recv()
are going to block the calling thread. If you want to avoid the "main" thread to get blocked, you could move the call to a separate "worker" thread, but then there still is no clean way to "abort" the operation from another thread. What you can do is setting up a time-out for these operations, so that the calling thread won't be blocked forever. Specifically, you may want to set SO_RCVTIMEO
and SO_SNDTIMEO
via the setsockopt()
function.Specify the receiving or sending timeouts until reporting an error. The argument is a struct timeval. If an input or output function blocks for this period of time, and data has been sent or received, the return value of that function will be the amount of data transferred; if no data has been transferred and the timeout has been reached, then -1 is returned with errno set to EAGAIN or EWOULDBLOCK, or EINPROGRESS (for connect(2)) just as if the socket was specified to be nonblocking. If the timeout is set to zero (the default), then the operation will never timeout. Timeouts only have effect for system calls that perform socket I/O (e.g., accept(2), connect(2), read(2), recvmsg(2), send(2), sendmsg(2)) [...] |
SOCK_NONBLOCK
flag to socket()
when creating the socket, or by using the fcntl()
function with the O_NONBLOCK
flag to switch an existing socket into non-blocking mode. This way, operations like connect()
, send()
and recv()
will not block at all, but rather return immediately with an error code like EAGAIN
or EWOULDBLOCK
(or EINPROGRESS
).poll()
(or epoll
) mechanism to figure out when a socket becomes ready for reading and/or writing:poll()
allows you to "monitor" multiple sockets at the same time, so you can react as soon as any of them becomes ready. Also, the poll()
function has an explicit time-out parameter, so you can be sure it will return after this amount of time.
MyConnection::connect()
from the main thread will spawn the worker thread (which will then perform the actual connect()
in the background) and return immediately. The main thread will later be informed that the connection has succeeded (or failed) via some sort of asynchronous “message” (e.g., using Window Messages or Qt Signals&Slots). Either that, or use some sort of polling mechanism, e.g., MyConnection::get_state()
, or even a (potentially) blocking MyConnection::await_connected()
.MyConnection::cancel()
from the main thread requests cancellation, but does not necessarily cancel the connection right away. Again, the main thread will be informed via an asynchronous “message” when the connection has actually been cancelled.TerminateProcess()
requests a process to be terminated, but does not terminate the process right away. Instead, after a successful TerminateProcess()
you still have to call the blocking WaitForSingleObject()
(can use optional timeout) or poll the status via GetExitCodeProcess()
, if you want to be sure that the process has actually terminated.
setsockopt
could be use to implement a timeout on blocking send/recv but there'd be no way to unblock from connect()
. Worker::Connect(CallbackMessage* cb)
, which inserts a "connect request" into the worker thread's message queue, and returns almost immediately. When the worker thread processes the connect request from its message queue, it performs a truly non-blocking connect() call [1]. When connecting succeeds, it pushes CallbackMessage
onto the main thread's message queue to notify main thread of successful connection. If non-blocking connect() fails (e.g., "connection refused"), the worker thread waits 1 second, and re-inserts the connect request back onto its message queue. Of course, I could elect to implement some other retry strategy (e.g., only retry N times instead of indefinitely). For my use case, I want indefinite retries until user decides to abort. WSAEventSelect
can be used to associate network events (FD_CONNECT and FD_CLOSE) with a non-blocking socket object and a "WSA" Event Object [2] (which is no different from a regular Event Object [2]; the WSA Event Object is atomically signaled when the network events you specified occurs). This WSA Event Object, along with any additional "regular" Event Objects can be used with WSAWaitForMultipleEvents
[3] to block until any one (or optionally all) of the events are signaled. A "regular" event object ("Abort Event Object") can be defined to indicate that the user wants to abort the connect operation. Therefore, while WSAWaitForMultipleEvents suspends the worker thread waiting for a network event (FD_CONNECT), it can also be woken up if the main thread signals the Abort Event Object. I've tested this technique and it's working.