How to Set the Timeout for a Tcpclient

How to set the timeout for a TcpClient?

You would need to use the async BeginConnect method of TcpClient instead of attempting to connect synchronously, which is what the constructor does. Something like this:

var client = new TcpClient();
var result = client.BeginConnect("remotehost", this.Port, null, null);

var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));

if (!success)
{
throw new Exception("Failed to connect.");
}

// we have connected
client.EndConnect(result);

C# TcpClient Timeout

The only way i know is to use the Async methods.
There is a nice new async method in .Net 4.5 which returns a Task that you could Wait like this:

tcpClient.ConnectAsync().Wait(timeout)

It returns a false if it doesn't succeed.

How to close the TcpClient with a timeout?

At the moment you are awaiting both the WriteAsync and ReadAsync calls. Because of this the timeout in your call to networkStream.Close(1000) should have no impact and the connection will always close immediately as no data is waiting to be sent/received. For neither the write or read you have specified a timeout, which means they won't return until data has finished being transferred.

I would like to know how to close the client with a timeout?

It's not clear why you want this or what you want to achieve with this. TcpClient is simply a wrapper around a NetworkStream which in turn wraps around a Socket. So handling timeouts both on the TcpClient and the NetworkStream doesn't make much sense.


Resource management:

In your current example I would first of all advise you to keep the TcpClient inside the Send method instead of a class field. If you don't need to use the TcpClient in other places (which expect you don't since you are closing it in the Send function) you should narrow it's scope for easier resource management. While doing that I'd suggest you make use of the using statement to avoid forgetting to properly dispose your resources. This applies to all types that implement the IDisposable interface (of course there are exceptions to this).

Handling timeout:

To handle timeouts in this snippet of code you have shared I suggest you configure the timeout on the write and read operations rather than the close operation, since your code is very sequential. An example of what that could look like:

class MyTcpService : IMyTcpService
{
public async Task Send(byte[] bytesToSend)
{
string responseMessage;
using (tcpClient = new TcpClient())
{
if (shouldUseTimeout) // From config
{
tcpClient.ReceiveTimeout = 1000; // From config
tcpClient.SendTimeout = 1000; // From config
}
await tcpClient.ConnectAsync("127.0.0.1", 5000); // Read values from config

NetworkStream networkStream = tcpClient.GetStream();

// Send the message
await networkStream.WriteAsync(bytesToSend, 0, bytesToSend.Length);

// Read the response
byte[] responseBuffer = new byte[1024]; // Read value from config
int amountOfResponseBytes = await networkStream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
responseMessage = Encoding.ASCII.GetString(responseBuffer, 0, amountOfResponseBytes);
}
// The tcpClient is now properly closed and disposed of

// Handle the response message here
// responseMessage...
}
}

Update in response to the comment 13/10/2020:

hey, thanks for your reply :) I tried to improve your answer, what do you think about this snippet? pastebin.com/7kTvtTv2

From your https://pastebin.com/7kTvtTv2:

public async Task<string> Send(byte[] messageToSend)
{
string responseMessage;

using (TcpClient tcpClient = new TcpClient())
{
await tcpClient.ConnectAsync("127.0.0.1", 5000); // From config

NetworkStream networkStream = tcpClient.GetStream();
await networkStream.WriteAsync(messageToSend, 0, messageToSend.Length);
await networkStream.FlushAsync();
tcpClient.Client.Shutdown(SocketShutdown.Send); // shutdown gracefully

byte[] responseBuffer = new byte[256]; // This can be of any size
StringBuilder stringBuilder = new StringBuilder();
int amountOfResponseBytes;

do
{
amountOfResponseBytes = await networkStream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
string responseData = Encoding.ASCII.GetString(responseBuffer, 0, amountOfResponseBytes);
stringBuilder.Append(responseData);
} while (amountOfResponseBytes > 0);

responseMessage = stringBuilder.ToString();
}

return responseMessage;
}

Looks pretty good to me. Only some minor comments:

await networkStream.FlushAsync() - it seems like this should be unnecessary when I look at the remarks for the Flush method but I haven't tested it:

The Flush method implements the Stream.Flush method; however, because NetworkStream is not buffered, it has no effect on network streams. Calling the Flush method does not throw an exception

tcpClient.Client.Shutdown(SocketShutdown.Send) - this method simply tells the Socket to no longer allow writing/sending data. Since your TcpClient only stays within the Send method and therefore not being shared anywhere it seems a little unnecessary too. I think the Shutdown method is mostly relevant if you don't have complete control over when the Socket is used.

do { ... } while (...) - looks good to me. Just remember the responseBuffer need to be a multiple of 8 if you are dealing with ASCII characters so you don't end up trying to decode a partial character.

Where did the timeout handling go? Did you forget to add timeout handling or is it not relevant anymore? Currently, if you have a lot of data to send or receive or the network is just slow, the WriteAsync and ReadAsync calls may potentially take a long time.

C# TcpClient Connect() gets timeout for unknown reason

[ANSWER]

I tried resetting my router/modem and that did not work. So, I changed the local IP on the server to something else (was using 192.168.0.2, I changed it to 192.168.0.4) and it worked.

I double checked to make sure there was no IP conflicts on my network and all seemed fine, so I am not sure why 192.168.0.2 was causing a failure to connect.

All is good now.

powershell TCPclient timeout per connection try

Here is an example of how you can implement the .ConnectAsync(..) Method from the TcpClient Class, which would allow to test async multiple TCP ports and with a specific TimeOut defined as argument.

Note, this function requires .NET Framework 4.5+ if running Windows PowerShell / .NET Core 1.0+ if running PowerShell Core.

using namespace System.Diagnostics
using namespace System.Collections.Generic
using namespace System.Net.Sockets
using namespace System.Threading.Tasks

function Test-TCPConnectionAsync {
[cmdletbinding()]
param(
[parameter(Mandatory, Valuefrompipeline, ValueFromPipelineByPropertyName)]
[alias('ComputerName', 'HostName', 'Host')]
[string[]] $Target,

[parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateRange(1, 65535)]
[int[]] $Port,

[parameter()]
[ValidateRange(5, [int]::MaxValue)]
[int] $TimeOut = 5, # In seconds!

[parameter()]
[switch] $IPv6
)

begin {
$timer = [Stopwatch]::StartNew()
$queue = [List[hashtable]]::new()
$TimeOut = [timespan]::FromSeconds($TimeOut).TotalMilliseconds
if($IPv6.IsPresent) {
$newTcp = { [TCPClient]::new([AddressFamily]::InterNetworkV6) }
return

}
$newTcp = { [TCPClient]::new() }
}
process {
foreach($item in $Target) {
foreach($i in $Port) {
$tcp = & $newTcp
$queue.Add(@{
Instance = $tcp
Task = $tcp.ConnectAsync($item, $i)
Output = [ordered]@{
Source = $env:COMPUTERNAME
Destination = $item
Port = $i
}
})
}
}
}
end {
while($queue -and $timer.ElapsedMilliseconds -le $timeout) {
try {
$id = [Task]::WaitAny($queue.Task, 200)
if($id -eq -1) {
continue
}
$instance, $task, $output = $queue[$id]['Instance', 'Task', 'Output']
if($instance) {
$instance.Dispose()
}
$output['Success'] = $task.Status -eq [TaskStatus]::RanToCompletion
$queue.RemoveAt($id)
[pscustomobject] $output
}
catch {
$PSCmdlet.WriteError($_)
}
}

foreach($item in $queue) {
try {
$instance, $task, $output = $item['Instance', 'Task', 'Output']
$output['Success'] = $task.Status -eq [TaskStatus]::RanToCompletion
if($instance) {
$instance.Dispose()
}
[pscustomobject] $output
}
catch {
$PSCmdlet.WriteError($_)
}
}
}
}

If you want to implement a parallel scan for multiple hosts, you can find an example using Runspaces on my GitHub.



Examples

'google.com', 'stackoverflow.com' | Test-TCPConnectionAsync 80, 443, 8080, 389, 636
@'
Target,Port
google.com,80
google.com,443
google.com,8080
google.com,389
google.com,636
cisco.com,80
cisco.com,443
cisco.com,8080
cisco.com,389
cisco.com,636
amazon.com,80
amazon.com,443
amazon.com,8080
amazon.com,389
amazon.com,636
'@ | ConvertFrom-Csv | Test-TCPConnectionAsync

Set timeout when reading NetworkStream with TcpClient

Data is transported over TCP in packets, which arrive serially (but not necessarily in the correct sequence). Immediately when data is available, i.e. when the logically (if not chronolgically) next packet is received, clientStream.Read() will return with the data in this (and maybe any other o-o-sequence) packet(s) - no matter if this is all data the sending side has sent or not.

Your Thread.Sleep() makes the program wait for a second - in this time more than one packets arrive and are buffered on the system level, so the call to clientStream.Read() will return immediately with the available data.

The correct way to handle this is to loop your Read() until BytesAvailable() becomes zero or a complete application-layer protocol element is detected.

TcpClient SocketException with timeout after 20s no matter what

It turns out that the remote host wasn't responding in a timely manner, hence the problem. Let me elaborate, and though this will be a solution very specific to my case maybe it will be useful for others too.

The real issue wasn't a timeout per se, as the exception indicated, but rather what exceptions thrown on subsequent Read() calls have shown: "An existing connection was forcibly closed by the remote host"

The remote host wasn't purposely closing the connection. Rather what happened is that when it was slow to respond it was actually so busy that it wasn't processing any TCP traffic either. While the local host wasn't explicitly sending anything while waiting for a response this still was an issue: the local host tried to send ACKs for previous transmissions of the remote host. Since these couldn't be delivered the local host determined that the remote host "forcibly closed" the connection.

I got the clue from looking at the traffic with Wireshark (always good to try to look at what's beneath the surface instead of guessing around): it was apparent that while the remote host was busy it showed complete radio silence. At the same time Wireshark showed retransmission attempts carried out by the local host, indicating that this is behind the issue.

Thus the solution couldn't be implemented on the local host either, the behavior of the remote host needed to be changed.

TCPClient : Custom timeout time

Winsock has no connect timeout, but this can be overcomed.

You have several options:

  1. Without threads:

    • Using non-blocking mode: call Connect, then wait using Winsock select function (encapsulated in TBaseSocket Select method inherited by TTcpClient).

    • Using blocking mode: changing temporarily to non-blocking mode and proceeding as in the previous case.

  2. With threads: see Remy Lebeau's answer to How to control the connect timeout with the Winsock API?.

  3. Use Indy.

Blocking vs non-blocking

Using blocking or non-blocking mode is a very important design decision that will affect many of your code and which you can't easily change afterward.

For example, in non-blocking mode, receive functions (as Receiveln), will not wait until there is enough input available and could return with an empty string. This can be an advantage if is this what you need, but you need to implement some strategy, such as waiting using TcpClient.WaitForData before calling the receive function (in your example, the Receiveln-Sendln-Receiveln will not work as is).

For simple tasks, blocking mode is easier to deal with.

Non-blocking mode

The following function will wait until the connection is successful or the timeout elapses:

function WaitUntilConnected(TcpClient: TTcpClient; Timeout: Integer): Boolean;
var
writeReady, exceptFlag: Boolean;
begin
// Select waits until connected or timeout
TcpClient.Select(nil, @writeReady, @exceptFlag, Timeout);
Result := writeReady and not exceptFlag;
end;

How to use:

// TcpClient.BlockMode must be bmNonBlocking

TcpClient.Connect; // will return immediately
if WaitUntilConnected(TcpClient, 500) then begin // wait up to 500ms
... your code here ...
end;

Also be aware of the following drawbacks/flaws in TTcpClient's non-blocking mode design:

  • Several functions will call OnError with SocketError set to WSAEWOULDBLOCK (10035).
  • Connected property will be false because is assigned in Connect.

Blocking mode

Connection timeout can be achieved by changing to non-blocking mode after socket is created but before calling Connect, and reverting back to blocking mode after calling it.

This is a bit more complicated because TTcpClient closes the connection and the socket if we change BlockMode, and also there is not direct way of creating the socket separately from connecting it.

To solve this, we need to hook after socket creation but before connection. This can be done using either the DoCreateHandle protected method or the OnCreateHandle event.

The best way is to derive a class from TTcpClient and use DoCreateHandle, but if for any reason you need to use TTcpClient directly without the derived class, the code can be easily rewriten using OnCreateHandle.

type
TExtendedTcpClient = class(TTcpClient)
private
FIsConnected: boolean;
FNonBlockingModeRequested, FNonBlockingModeSuccess: boolean;
protected
procedure Open; override;
procedure Close; override;
procedure DoCreateHandle; override;
function SetBlockModeWithoutClosing(Block: Boolean): Boolean;
function WaitUntilConnected(Timeout: Integer): Boolean;
public
function ConnectWithTimeout(Timeout: Integer): Boolean;
property IsConnected: boolean read FIsConnected;
end;

procedure TExtendedTcpClient.Open;
begin
try
inherited;
finally
FNonBlockingModeRequested := false;
end;
end;

procedure TExtendedTcpClient.DoCreateHandle;
begin
inherited;
// DoCreateHandle is called after WinSock.socket and before WinSock.connect
if FNonBlockingModeRequested then
FNonBlockingModeSuccess := SetBlockModeWithoutClosing(false);
end;

procedure TExtendedTcpClient.Close;
begin
FIsConnected := false;
inherited;
end;

function TExtendedTcpClient.SetBlockModeWithoutClosing(Block: Boolean): Boolean;
var
nonBlock: Integer;
begin
// TTcpClient.SetBlockMode closes the connection and the socket
nonBlock := Ord(not Block);
Result := ErrorCheck(ioctlsocket(Handle, FIONBIO, nonBlock)) <> SOCKET_ERROR;
end;

function TExtendedTcpClient.WaitUntilConnected(Timeout: Integer): Boolean;
var
writeReady, exceptFlag: Boolean;
begin
// Select waits until connected or timeout
Select(nil, @writeReady, @exceptFlag, Timeout);
Result := writeReady and not exceptFlag;
end;

function TExtendedTcpClient.ConnectWithTimeout(Timeout: Integer): Boolean;
begin
if Connected or FIsConnected then
Result := true
else begin
if BlockMode = bmNonBlocking then begin
if Connect then // will return immediately, tipically with false
Result := true
else
Result := WaitUntilConnected(Timeout);
end
else begin // blocking mode
// switch to non-blocking before trying to do the real connection
FNonBlockingModeRequested := true;
FNonBlockingModeSuccess := false;
try
if Connect then // will return immediately, tipically with false
Result := true
else begin
if not FNonBlockingModeSuccess then
Result := false
else
Result := WaitUntilConnected(Timeout);
end;
finally
if FNonBlockingModeSuccess then begin
// revert back to blocking
if not SetBlockModeWithoutClosing(true) then begin
// undesirable state => abort connection
Close;
Result := false;
end;
end;
end;
end;
end;
FIsConnected := Result;
end;

How to use:

TcpClient := TExtendedTcpClient.Create(nil);
try
TcpClient.BlockMode := bmBlocking; // can also be bmNonBlocking

TcpClient.RemoteHost := 'www.google.com';
TcpClient.RemotePort := '80';

if TcpClient.ConnectWithTimeout(500) then begin // wait up to 500ms
... your code here ...
end;
finally
TcpClient.Free;
end;

As noted before, Connected doesn't work well with non-blocking sockets, so I added a new IsConnected property to overcome this (only works when connecting with ConnectWithTimeout).

Both ConnectWithTimeout and IsConnected will work with both blocking and non-blocking sockets.



Related Topics



Leave a reply



Submit