UFO ET IT

TcpClient에 대한 시간 제한을 설정하는 방법은 무엇입니까?

ufoet 2020. 11. 9. 21:41
반응형

TcpClient에 대한 시간 제한을 설정하는 방법은 무엇입니까?


원격 컴퓨터의 수신기에 데이터를 보내는 데 사용하는 TcpClient가 있습니다. 원격 컴퓨터는 때때로 켜져 있고 때로는 꺼집니다. 이로 인해 TcpClient가 자주 연결되지 않습니다. TcpClient가 1 초 후에 시간 초과되기를 원하므로 원격 컴퓨터에 연결할 수 없을 때 시간이 많이 걸리지 않습니다. 현재 TcpClient에이 코드를 사용합니다.

try
{
    TcpClient client = new TcpClient("remotehost", this.Port);
    client.SendTimeout = 1000;

    Byte[] data = System.Text.Encoding.Unicode.GetBytes(this.Message);
    NetworkStream stream = client.GetStream();
    stream.Write(data, 0, data.Length);
    data = new Byte[512];
    Int32 bytes = stream.Read(data, 0, data.Length);
    this.Response = System.Text.Encoding.Unicode.GetString(data, 0, bytes);

    stream.Close();
    client.Close();    

    FireSentEvent();  //Notifies of success
}
catch (Exception ex)
{
    FireFailedEvent(ex); //Notifies of failure
}

이것은 작업을 처리하기에 충분합니다. 가능한 경우 전송하고 원격 컴퓨터에 연결할 수없는 경우 예외를 포착합니다. 그러나 연결할 수없는 경우 예외를 throw하는 데 10 ~ 15 초가 걸립니다. 약 1 초 후에 타임 아웃해야합니까? 타임 아웃 시간은 어떻게 변경합니까?


생성자가하는 일을 동 기적으로 연결 BeginConnect하는 TcpClient대신 비동기 메서드 를 사용해야합니다 . 이 같은:

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);

.NET 4.5부터 TcpClient에는 다음 과 같이 사용할 수 있는 멋진 ConnectAsync 메서드가 있으므로 이제 매우 쉽습니다.

var client = new TcpClient();
if (!client.ConnectAsync("remotehost", remotePort).Wait(1000))
{
    // connection failure
}

https://stackoverflow.com/a/25684549/3975786을 사용하는 또 다른 대안 :

var timeOut = TimeSpan.FromSeconds(5);     
var cancellationCompletionSource = new TaskCompletionSource<bool>();
try
{
    using (var cts = new CancellationTokenSource(timeOut))
    {
        using (var client = new TcpClient())
        {
            var task = client.ConnectAsync(hostUri, portNumber);

            using (cts.Token.Register(() => cancellationCompletionSource.TrySetResult(true)))
            {
                if (task != await Task.WhenAny(task, cancellationCompletionSource.Task))
                {
                    throw new OperationCanceledException(cts.Token);
                }
            }

            ...

        }
    }
}
catch(OperationCanceledException)
{
    ...
}

주의해야 할 사항은 시간 초과가 만료되기 전에 BeginConnect 호출이 실패 할 수 있다는 것입니다. 이것은 로컬 연결을 시도하는 경우 발생할 수 있습니다. 다음은 Jon 코드의 수정 된 버전입니다.

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

        result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));
        if (!client.Connected)
        {
            throw new Exception("Failed to connect.");
        }

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

위의 답변은 시간 초과 된 연결을 깔끔하게 처리하는 방법을 다루지 않습니다. TcpClient.EndConnect를 호출하고 성공하지만 시간 초과 후에 연결을 닫고 TcpClient를 삭제합니다.

과잉 일 수 있지만 이것은 나를 위해 작동합니다.

    private class State
    {
        public TcpClient Client { get; set; }
        public bool Success { get; set; }
    }

    public TcpClient Connect(string hostName, int port, int timeout)
    {
        var client = new TcpClient();

        //when the connection completes before the timeout it will cause a race
        //we want EndConnect to always treat the connection as successful if it wins
        var state = new State { Client = client, Success = true };

        IAsyncResult ar = client.BeginConnect(hostName, port, EndConnect, state);
        state.Success = ar.AsyncWaitHandle.WaitOne(timeout, false);

        if (!state.Success || !client.Connected)
            throw new Exception("Failed to connect.");

        return client;
    }

    void EndConnect(IAsyncResult ar)
    {
        var state = (State)ar.AsyncState;
        TcpClient client = state.Client;

        try
        {
            client.EndConnect(ar);
        }
        catch { }

        if (client.Connected && state.Success)
            return;

        client.Close();
    }

동기 읽기 / 쓰기를 위해 NetworkStream에서 ReadTimeout 또는 WriteTimeout 속성을 설정합니다. OP 코드 업데이트 :

try
{
    TcpClient client = new TcpClient("remotehost", this.Port);
    Byte[] data = System.Text.Encoding.Unicode.GetBytes(this.Message);
    NetworkStream stream = client.GetStream();
    stream.WriteTimeout = 1000; //  <------- 1 second timeout
    stream.ReadTimeout = 1000; //  <------- 1 second timeout
    stream.Write(data, 0, data.Length);
    data = new Byte[512];
    Int32 bytes = stream.Read(data, 0, data.Length);
    this.Response = System.Text.Encoding.Unicode.GetString(data, 0, bytes);

    stream.Close();
    client.Close();    

    FireSentEvent();  //Notifies of success
}
catch (Exception ex)
{
    // Throws IOException on stream read/write timeout
    FireFailedEvent(ex); //Notifies of failure
}

다음은 mcandal 솔루션을 기반으로 한 코드 개선입니다 . client.ConnectAsync작업 에서 생성 된 모든 예외에 대한 예외 포착 추가 (예 : 서버에 연결할 수없는 경우 SocketException)

var timeOut = TimeSpan.FromSeconds(5);     
var cancellationCompletionSource = new TaskCompletionSource<bool>();

try
{
    using (var cts = new CancellationTokenSource(timeOut))
    {
        using (var client = new TcpClient())
        {
            var task = client.ConnectAsync(hostUri, portNumber);

            using (cts.Token.Register(() => cancellationCompletionSource.TrySetResult(true)))
            {
                if (task != await Task.WhenAny(task, cancellationCompletionSource.Task))
                {
                    throw new OperationCanceledException(cts.Token);
                }

                // throw exception inside 'task' (if any)
                if (task.Exception?.InnerException != null)
                {
                    throw task.Exception.InnerException;
                }
            }

            ...

        }
    }
}
catch (OperationCanceledException operationCanceledEx)
{
    // connection timeout
    ...
}
catch (SocketException socketEx)
{
    ...
}
catch (Exception ex)
{
    ...
}

async & await를 사용하고 차단하지 않고 시간 초과를 사용하려는 경우 mcandal이 제공하는 대답의 대안적이고 간단한 접근 방식은 백그라운드 스레드에서 연결을 실행하고 결과를 기다리는 것입니다. 예를 들면 :

Task<bool> t = Task.Run(() => client.ConnectAsync(ipAddr, port).Wait(1000));
await t;
if (!t.Result)
{
   Console.WriteLine("Connect timed out");
   return; // Set/return an error code or throw here.
}
// Successful Connection - if we get to here.

See the Task.Wait MSDN article for more info and other examples.


As Simon Mourier mentioned, it's possible to use ConnectAsync TcpClient's method with Task in addition and stop operation as soon as possible.
For example:

// ...
client = new TcpClient(); // Initialization of TcpClient
CancellationToken ct = new CancellationToken(); // Required for "*.Task()" method
if (client.ConnectAsync(this.ip, this.port).Wait(1000, ct)) // Connect with timeout as 1 second
{

    // ... transfer

    if (client != null) {
        client.Close(); // Close connection and dipose TcpClient object
        Console.WriteLine("Success");
        ct.ThrowIfCancellationRequested(); // Stop asynchronous operation after successull connection(...and transfer(in needed))
    }
}
else
{
    Console.WriteLine("Connetion timed out");
}
// ...

참고URL : https://stackoverflow.com/questions/17118632/how-to-set-the-timeout-for-a-tcpclient

반응형