[c#] 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
}

이것은 작업을 처리하기에 충분합니다. 가능한 경우 전송하고 원격 컴퓨터에 연결할 수없는 경우 예외를 포착합니다. 그러나 연결할 수없는 경우 예외가 발생하는 데 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)
{
    ...
}


답변

위의 답변은 시간 초과 된 연결을 깔끔하게 처리하는 방법을 다루지 않습니다. 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();
    }


답변

주의해야 할 한 가지는 시간 초과가 만료되기 전에 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);


답변

동기 읽기 / 쓰기를 위해 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)
{
    ...
}