Yaron 53 Posted January 21, 2020 (edited) Using Delphi 10.3.3: My goal is to have a stable client/server remote control over TCP/IP that can automatically recover in cases where the network disconnects occasionally. I use a structure Remi suggested of creating a reading thread that tries to readln with a 1sec time out: FClient.OnDisconnected := OnDisconnect; FClient.IOHandler.ReadTimeout := 1000; // lets us exit the read operation after 1sec of inactivity While Terminated = False do Begin If FClient.Connected = True then Try FData := FClient.IOHandler.ReadLn(IndyTextEncoding_UTF8); Except // catch exception & update UI Try If FClient.Connected = True then FClient.Disconnect; except on E: Exception do Begin // Catch disconnect exception End; end; End; End; I have a client PC connected to a router via cable and the server PC connected to the same router via WiFi. After connecting the client to the server, I disconnect the server's wifi access and absolutely nothing happens (I waited over a minute). The OnDisconnected event isn't triggered, calling FClient.IOHandler.ReadLn doesn't return any exception and FClient.Connected remains True. If I then try to call FClient.IOHandler.WriteLn, within about 10 seconds a "Socket Error # 10054 - Connection reset by peer" exception is raised on the ReadLn code about (not on the WriteLn function). In that exception, I call "If FClient.Connected = True then FClient.Disconnect" which raises another "Socket Error # 10054 - Connection reset by peer" exception. Finally, my code loops to the beginning and then the same exception is triggered on the initial "If FClient.Connected = True" line. Obviously, I'm not handling things correctly, I wasn't expecting "If FClient.Connected" to raise exceptions and I was hoping the OnDisconnect event to trigger, but are there other pitfalls I might be missing? Edited January 21, 2020 by Yaron Share this post Link to post
Remy Lebeau 1394 Posted January 21, 2020 (edited) 3 hours ago, Yaron said: I use a structure Remi suggested of creating a reading thread that tries to readln with a 1sec time out: That structure is incomplete for what I would have suggested for this kind of situation. See further below. Quote The OnDisconnected event isn't triggered The OnDisconnect event is not called until the Disconnect() method is called. You really shouldn't be using the OnDisconnect event in this situation, though. Quote calling FClient.IOHandler.ReadLn doesn't return any exception The TIdIOHandler.ReadLn() method does not raise an exception on timeout (see this and this). Instead, it sets the TIdIOHandler.ReadLnTimedOut property to True and returns a blank string, which you are not checking for. Quote FClient.Connected remains True. The Connected() method will return True if there is any unread data in the TIdIOHandler.InputBuffer that can continue to satisfy read operations without having to go back to the underlying socket to get more data. Once the InputBuffer is exhausted, only then does Connected() check the socket, and if the socket has been *gracfully* closed, or there is a reading error, THEN it returns False. Quote If I then try to call FClient.IOHandler.WriteLn, within about 10 seconds a "Socket Error # 10054 - Connection reset by peer" exception is raised on the ReadLn code about (not on the WriteLn function). Simply shutting off the WiFi does not close the socket connection *gracefully*, so it may take time for the OS to detect the TCP connection is dead and invalidate the socket so read/write operations can start reporting errors. If you find that the OS takes too long to report dead connections, you can enable TCP-level keep-alives on the underlying socket. Or simply use your own timeouts in your code and bail out manually if your timeouts elapse. Quote In that exception, I call "If FClient.Connected = True then FClient.Disconnect" which raises another "Socket Error # 10054 - Connection reset by peer" exception. Connected() should not be raising an exception. Although Connected() may perform a read operation, it should be ignoring any socket errors on that read. If that is not the case, then please file a bug report. Which version of Indy are you using exactly? Disconnect() calls Connected() internally if its optional ANotifyPeer parameter is True. When a socket read/write exception occurs, you don't really know the state of the communication anymore, so the only viable thing to do is to just close the connection immediately and not try to exchange any more data. Try calling Disconnect() with its ANotifyPeer parameter set to False (it is True by default). Quote Finally, my code loops to the beginning and then the same exception is triggered on the initial "If FClient.Connected = True" line. Because you weren't able to fully disconnect. Quote Obviously, I'm not handling things correctly, I wasn't expecting "If FClient.Connected" to raise exceptions and I was hoping the OnDisconnect event to trigger, but are there other pitfalls I might be missing? You really shouldn't be using Connected() at all. Try something more like this instead: FClient.ConnectTimeout := 5000; FClient.ReadTimeout := 1000; while not Terminated do begin try FClient.Connect; except // handle connect error as needed... for I := 1 to 5 do begin if Terminated then Break; Sleep(1000); end; continue; end; try FClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8; while not Terminated do begin FData := FClient.IOHandler.ReadLn; if (FData = '') and FClient.IOHandler.ReadLnTimedOut then begin // handle timeout as needed... end else begin // handle data as needed... end; end; FClient.Disconnect; except FClient.Disconnect(False); // handle exception as needed... end; end; Edited January 21, 2020 by Remy Lebeau 1 Share this post Link to post
Lars Fosdal 1792 Posted January 22, 2020 What about EIdConnClosedGracefully? That is a kind of an annoying one. Share this post Link to post
Remy Lebeau 1394 Posted January 22, 2020 9 hours ago, Lars Fosdal said: What about EIdConnClosedGracefully? What about it? Share this post Link to post
dummzeuch 1505 Posted January 22, 2020 10 hours ago, Lars Fosdal said: What about EIdConnClosedGracefully? That is a kind of an annoying one. Why? Simply add it to the list of exceptions the debugger should ignore and you will never see it again. Share this post Link to post
Dave Nottage 557 Posted January 22, 2020 3 hours ago, Remy Lebeau said: What about it? It's the most annoying exception, ever. It's a permanent "ignore" in my exception tracking code. It'd be great if it was just removed, permanently. Share this post Link to post
Remy Lebeau 1394 Posted January 22, 2020 37 minutes ago, Dave Nottage said: It's the most annoying exception, ever. It's a permanent "ignore" in my exception tracking code. It'd be great if it was just removed, permanently. I disagree that it should be removed. It is a legitimate runtime error condition. Data was attempted to be read/sent over a socket after the socket was closed. If you choose to have your debugger ignore that exception, that is entirely up to you. Doesn't mean the exception itself doesn't have meaning or value. Share this post Link to post
Dave Nottage 557 Posted January 23, 2020 5 hours ago, Remy Lebeau said: Data was attempted to be read/sent over a socket after the socket was closed OK, for some reason I was under the impression that it was being raised when the socket was closed without an attempted read/write. I'll have to revisit that, thanks 🙂 Share this post Link to post
Remy Lebeau 1394 Posted January 23, 2020 49 minutes ago, Dave Nottage said: OK, for some reason I was under the impression that it was being raised when the socket was closed without an attempted read/write. Nope. Now THAT would be a stupid thing for it to do If you close the socket yourself, it should just close, no mess, no fuss. Any read/write operation performed on the socket AFTER you or the peer has closed the connection will fail with EIdConnClosedGracefully. Share this post Link to post
Lars Fosdal 1792 Posted January 23, 2020 The reason is that I seem to get it without having had a disconnect event. If I have had observed the disconnect, I obviously would not try to send data on a closed socket. Share this post Link to post
Fr0sT.Brutal 900 Posted January 23, 2020 3 minutes ago, Lars Fosdal said: The reason is that I seem to get it without having had a disconnect event. If I have had observed the disconnect, I obviously would not try to send data on a closed socket. How can you observe the disconnect on blocking sockets unless you try to read/write? Share this post Link to post
Lars Fosdal 1792 Posted January 23, 2020 I've had the socket open doing writes and reads. I still keep the connection open as I intend to send more shortly. If the remote point then disconnects gracefully - should I not be notified? Share this post Link to post
Fr0sT.Brutal 900 Posted January 23, 2020 3 hours ago, Lars Fosdal said: I've had the socket open doing writes and reads. I still keep the connection open as I intend to send more shortly. If the remote point then disconnects gracefully - should I not be notified? How do you imagine that notification with blocking sockets where you usually do a loop until recv or send returns -1? Even select won't tell whether connection has been reset. Share this post Link to post
Lars Fosdal 1792 Posted January 23, 2020 When you put it that way, I wonder why I had the notion that a remote TCP host's willful disconnect actually signaled the connected. Share this post Link to post
Remy Lebeau 1394 Posted January 24, 2020 On 1/22/2020 at 11:56 PM, Lars Fosdal said: The reason is that I seem to get it without having had a disconnect event. Client side or server side? On the client side, OnDisconnect is only fired when Disconnect() is called, it is not an asynchronous event. Indy is largely synchronous only, so it is hard to get this exception without do something to trigger it. Server side might make more sense, since Indy servers generally run a continuous reading thread, so the exception is more likely to happen on that side, but just let the server handle it internally for you. 1 Share this post Link to post
Remy Lebeau 1394 Posted January 24, 2020 (edited) On 1/23/2020 at 12:05 AM, Lars Fosdal said: I've had the socket open doing writes and reads. I still keep the connection open as I intend to send more shortly. If the remote point then disconnects gracefully - should I not be notified? Not when using blocking sockets, no. There are no asynchronous events in that situation. A remote disconnect is reported only when an I/O operation fails due to the peer having already closed the connection on their end. A peer disconnecting gracefully is typically discovered as a read operation that returns 0 bytes rather than an error code. That is the condition that Indy uses to raise EIdConnClosedGracefully for a remote disconnect, so a read has to be performed to get that exception in most cases. Edited January 24, 2020 by Remy Lebeau 1 Share this post Link to post
pcplayer99 11 Posted March 3, 2020 I have some experience that using Indy TCP client to remote control some device through WiFi. My experience is that when the distance between mobile and device is out of range, you can test the connection by send some data and wait it timeout and in except section to close connection, like this: try IdTCPClient1.Send(....); except IdTCPClient1.Disconnect; end; and then try re-connect again, and again, and when mobile is into the range of device WiFi, you can connect again. Share this post Link to post