How to detect TCP close?

Hi,

I'd like to detect TCP client close in server side.
I carefully looked at the document: https://doc.rust-lang.org/std/net/index.html and try to write some codes. But I couldn't detect it. Could you tell me how to detect it? And, I wouldn't like to do extra-write to detect client-close.

You get a 0-sized read from it.

Thank you very much for your reply.

I tried to read with 0-sized buffer as follows.

use std::io::Read;

fn main() -> std::io::Result<()> {
    let l = std::net::TcpListener::bind("127.0.0.1:3000")?;
    loop {
        let (mut stream, _) = l.accept()?;
        println!("new stream: {:?}", stream);
        loop {
            let mut buf = [];
            let r = stream.read(&mut buf);
            println!("read result: {:?}", r);
            std::thread::sleep(std::time::Duration::from_secs(1));
        }
    }
}

But, .read() always returns Ok(()) after Ctrl+C.

Ah, no, not that way :-). Sorry for not being clearer about that. What happens if you ask for reading 0 bytes really depends on the OS and such and mostly you don't want to deal with the differences.

When you read data (with non-empty buffer), you can get:

  • An error.
  • Some bytes (between 1 and the size you asked for ‒ and yes, it's perfectly legal for read to give you less than you asked for)
  • 0 bytes. This signals there will never ever be more bytes, in other words, the other end has closed it (this is a bit violated by file-based pipes that can be closed multiple times, so you can get more bytes after a 0-sized read, but it's not your case).

If you use something „more clever“ than read (maybe read_exact), then that 0 bytes can get turned into an io::Error with UnexpectedEof error kind.

1 Like

Thanks.

I could detect close with non-empty([u8; 1]) buffer and read_exact() in my environment: macOS causing an error Err(Custom { kind: UnexpectedEof, error: "failed to fill whole buffer" }).

Is there any way to detect close without read/write (e.g. detect it with channel)? Reading or writing non-empty buffer has side-effects.

You could use peek instead of reading. But note that it will still block if the connection is open but no unread data is sent.

I think there is no way to detect dead closed peer without write at least one byte.
So if you sure to want to know close, you must write something.

cite:


2.8 Why does it take so long to detect that the peer died?
Because by default, no packets are sent on the TCP connection unless there is data to send or acknowledge.

Generally speaking, you won't know if a client is still available or not. They may explicitly disconnect, but they may never do so, either due to network issues, poorly coded client software, or maliciousness. Reading for zero bytes will tell you when a client disconnects, but that's not always sufficient for appropriate client management.

Often network protocols will include the concept of a keepalive or a heartbeat, so that a server can know when clients are no longer responsive, whether due to an explicit disconnection, lost packets, network partitions, or other reasons.

For example: The protocol might require that clients to send a specific message, like {"msg":"keepalive"} or HEARTBEAT\n\n after every 10 seconds of inactivity, and then if you haven't received a keepalive or other message from a client for 30 seconds, assume they are no longer available, and disconnect from the server side.

The protocol may also specify that clients and servers send a specific message before disconnecting, but this only helps with well-behaved clients.

See, for example: https://en.wikipedia.org/wiki/HTTP_persistent_connection for how connections are managed in HTTP.

There is some good information in your post, but it conflates some things that shouldn't be conflated in my opinion:

  • If the client simply disappears (e.g. suffered a power outage) it's of course fundamentally impossible to detect without a heartbeat. I don't think this is the condition the OP meant with "TCP close".
  • If the client's TCP stack sends an explicit RST, the server's OS exposes this to the application by returning an error to any blocking read or write operation on the socket. I think this is the condition the OP was asking about and I also find it surprising that there is afaik no API to detect this connection state without reading (and storing/processing) some bytes from the socket.
  • As you mention HTTP it might be worth mentioning that the Websocket protocol (which could be considered part of HTTP) has a "closing handshake" to inform the server about a client going away, but I'm not sure if it is used by browsers.

I'm tying to imagine a situation where one would want to know or care if a socket was closed if one is not trying to read from it.

Conversely, if one does want to know when a connection is closed and does not care to read anything from it, then why not just do a read from it anyway, in the hope that it never returns, unless it returns with zero bytes when the socket closes.

Or, the third option, one does want to know when the socket closes and one does want to read from it. In which case zero bytes returned by read indicates the sockets is closed.

Surely that covers all the possibilities and nothing else is needed in the API to do the job?

I don't remember where it was, but I once had such a situation. I wanted to check beforehand if the connection is still "good", and if it was, I wanted to hand the socket over to a library that would do the reading and processing. Because there was no such API I had to read into a buffer myself and then hand over this buffer to the library. Just a minor inconvenience.

Hmmm.... that sounds a bit odd to me.

If you check the socket is "good" and then you hand it over to something else, there is no guarantee that it is still good when they get hold of it. Kind of a race condition.

Then what happens? Who ever is doing the reading will fail because the socket is closed, and has to handle it some how. Might as well not have checked the socket in the first place.

Well, I have to handle unexpected errors in the middle of the processing anyway. (By throwing an exception, it wasn't in Rust.). The difference is just that a closed connection is a normal case and should be handled with a different error message.