Search code examples
c++boosttcpboost-asioboost-beast

Boost::Asio async client - how to know the other side did TCP FIN?


I am using a boost::beast::tcp_stream which wraps around a boost::asio::basic_stream_socket for an asynchronous client.

No. Time           Source                Destination           Protocol Length Info
...
117 0.261388597    127.0.0.1             127.0.0.1             HTTP/JSON 125    HTTP/1.1 200 OK , JavaScript Object Notation (application/json)
119 0.261398240    127.0.0.1             127.0.0.1             TCP      66     43794 → 8090 [ACK] Seq=47 Ack=257 Win=65408 Len=0 TSval=4119348562 TSecr=4119348562
121 0.261485938    127.0.0.1             127.0.0.1             TCP      66     8090 → 43794 [FIN, ACK] Seq=257 Ack=47 Win=65536 Len=0 TSval=4119348562 TSecr=4119348562

122 0.273668580    127.0.0.1             127.0.0.1             HTTP     115    POST /api/clear HTTP/1.1 
123 0.273716187    127.0.0.1             127.0.0.1             TCP      54     8090 → 43794 [RST] Seq=258 Win=0 Len=0

The client sends a request to the server (not shown), and the server responds with HTTP 200 OK.

The client naturally returns an ACK.

The server is Python Flask, which has a habit of closing the connection even with HTTP 1.1, so it sends a FIN, ACK.

The client wishes to send the next HTTP request, and it definitely does a boost::asio::io_context::poll() to give it all the opportunity to receive this FIN from the server. The client then checks boost::beast::tcp_stream::socket().is_open(), and it falsely returns true, even though it has received the FIN from the server.

The only way I can see to check if the connection is closed is to attempt to write the next request, which the server complains about with an RST.

Surely there is a way to receive the FIN from the server?


Solution

  • Q. The client wishes to send the next HTTP request, and it definitely does a boost::asio::io_context::poll() to give it all the opportunity to receive this FIN from the server.

    You don't receive FIN. You observe it when attempting the next operation. There is a chance that async_wait with wait_type::read|wait_type::error can lead to earlier information. But the nature of stream sockets is that you will know on your next read/write. (Same for file stream EOF, really).

    Q. The client then checks boost::beast::tcp_stream::socket().is_open(), and it falsely returns true, even though it has received the FIN from the server.

    You mean, it correctly returns true to reflect the socket is open. Even if the remote end shutdown the communications, the socket will remain open until you close it. That's important because you might depend on the error state, out-of-band communication or pending buffered data. It is also important to avoid race conditions where the same native handle gets re-used before you even notice the socket had been closed.

    Q. The only way I can see to check if the connection is closed is to attempt to write the next request, which the server complains about with an RST.

    That's the way.

    Q. Surely there is a way to receive the FIN from the server?

    • "Surely" you would be operating on the wrong OSI layer. So, yes, by all means, implement your own IP stack and design a better socket API on top that gives you this. Or, just live with the de-facto standards (POSIX, BSD, Winsock) that all behave the way you know.

    • I think the most async clients commonly have an async_read pending at all times (as part of full-duplex communication). This will already tell you immediately when the server goes away. Or fails.

    • Note that there may be socket options (I'm not an expert, but I think SO_KEEPALIVE is close?) that will allow you to detect errors earlier. But (a) the behaviour may be non-portable (b) it may not be convenient in the framework of your choice (IOW it might not actually help unless you use custom - non-ASIO - interfaces). Just throwing it out there for your information.