-1

Should I use non-blocking or blocking TCP sockets when using an I/O multiplexing API like poll(2) or epoll(2)?

Some people suggest using non-blocking sockets here but the I/O multiplexing APIs inform you anyway if there is data to read so what is wrong with a blocking socket here?

Emil Engler
  • 489
  • 5
  • 14

2 Answers2

4

If your TCP server is single-threaded and uses blocking I/O, then it's likely that any client that connects to it will be able to deny service to all of the other clients simply by sending only a partial-message, or alternatively by refusing to read any data from its TCP socket after the server sends data. In the former case, the server may block for a long time (perhaps forever) waiting for the entire message to be received from the client; during that time, the server will not be able to respond to other clients. In the latter case, the server will block for a long time (perhaps forever) waiting the client to read some TCP data so that the server-socket's send-buffer can be drained enough to fit some more outgoing data to that client.

One way to avoid that problem is to set all of the server's sockets to non-blocking I/O mode; that way the server knows it can never get "stuck" inside a recv() or a send() call, and thus can remain responsive to all clients regardless of whether any particular client is behaving nicely, or not. In the non-blocking design, the only place the server ever blocks is inside select() or poll() or similar, because those calls are designed to return whenever any client needs service, rather than blocking on only a single client. (the tradeoff is that with non-blocking I/O your server's buffering/queueing logic will need to be a bit more elaborate, since you can no longer assume that any particular fixed number of bytes will be sent or received during any given send or receive operation)

The other way to avoid the problem is to make a multi-threaded server; that has the advantage that each client gets its own thread, and therefore a badly-behaved client will block only its own thread and not the threads servicing other clients. The disadvantage is that now your server is multi-threaded, with all of the additional pitfalls that multithreading introduces.

(and, for completeness, the third approach is simply to ignore the possibility of badly-behaved/poorly-connected clients, and use a single-threaded/blocking model. That works fine for toy examples where clients are expected to be non-hostile, and where the network they are connecting over is reliable, but doesn't work so well in real life)

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • For DDOS prevention one also needs a timer in the same I/O thread to be able to prune TCP connections idle for some time. – Maxim Egorushkin Aug 19 '20 at 19:32
  • That would work, or just find the longest-idle TCP connection and disconnect it whenever you've reached your max-simultaneous-connections limit. – Jeremy Friesner Aug 19 '20 at 20:28
1

Non-blocking IO is used when you prefer an error response (EWOULDBLOCK / EAGAIN) over your thread waiting (blocking) until an IO operation becomes possible.

This leads to the question of how is the IO multiplexing achieved?

If you're using a thread-per-connection model (or a process-per-connection), using blocking IO might be more comfortable.

However, if the same thread is serving multiple IO objects, blocking IO would be hazardous and could bring the whole application to a halt.

It is better to use non-blocking IO when a single thread serves multiple IO objects.

Note that the issue might not be noticeable at first when polling (using select / poll or epoll/kqueue).

Since the IO operations are only performed by a code path that already "knows" that the IO operation will not block (it was polled and known to be an available operation).

This masks the issue that somewhere in the code an IO operation might be called directly without polling first, resulting in a blocking IO call that will grind the application to a halt.

Myst
  • 18,516
  • 2
  • 45
  • 67
  • 1
    Note that under Linux, `recv()` can block even if `select()` previously indicated that the socket is ready-for-read. See the fourth paragraph of the "BUGS" section of the Linux man-page for `select()`: https://man7.org/linux/man-pages/man2/select.2.html – Jeremy Friesner Aug 19 '20 at 18:24