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


79

원격 컴퓨터의 수신기에 데이터를 보내는 데 사용하는 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 초 후에 타임 아웃해야합니까? 타임 아웃 시간은 어떻게 변경합니까?

답변:


97

생성자가 수행하는 작업 인 동기 연결을 시도하는 대신 비동기 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);

2
비동기 연결을 사용하고 Wait와 다시 "동기화"하는 이유는 무엇입니까? 내 말은, 현재 비동기 읽기로 시간 제한을 구현하는 방법을 이해하려고 노력하고 있지만 해결책은 비동기 디자인을 완전히 비활성화하는 것이 아닙니다. 소켓 타임 아웃이나 취소 토큰 등을 사용해야합니다. 그렇지 않으면 대신 연결 / 읽기를 사용하십시오 ...
RoeeK 2014

5
@RoeeK : 요점은 연결 시도에 대한 임의의 시간 제한을 프로그래밍 방식으로 선택하는 것입니다. 이것은 비동기 IO를 수행하는 방법에 대한 예가 아닙니다.
Jon

9
@RoeeK :이 질문의 요점은 TcpClient제안 된 솔루션 중 하나 인 구성 가능한 시간 제한이있는 동기화 연결 기능을 제공하지 않는다는 것입니다. 이것은 활성화하기위한 해결 방법입니다. 반복하지 않고 다른 말을해야할지 모르겠습니다.
Jon

당신이 절대적으로 옳습니다. 그것이 정말 필요하지 어디 아마 비동기 작업에 너무 많은 "WaitOnes"를 본 적이 ..
RoeeK

2
@JeroenMostert는 이것을 지적 해주셔서 감사하지만 이것은 프로덕션 등급 코드가 아님을 명심합시다. 여러분의 프로덕션 시스템에서 "이와 같은 것"이라는 주석과 함께 제공되는 붙여 넣기 코드를 복사하지 마십시오. =)
Jon

83

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

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

3
ConnectAsync의 추가 이점은 Task.Wait가 시간 초과 이전에도 필요한 경우에 즉시 중지하도록 CancellationToken을 허용 할 수 있다는 것입니다.
일리아 Barahovski

9
.Wait는 동기식으로 차단하여 "Async"부분의 이점을 제거합니다. stackoverflow.com/a/43237063/613620 은 더 나은 완전 비동기 구현입니다.
Tim P.

9
@TimP. 질문에서 "비동기"라는 단어를 어디에서 보았습니까?
Simon Mourier

나는 그것이 좋은 대답이라고 생각하지만 반환 client.Connected; 내 테스트 케이스 혼자 기다리는 것은 확실한 대답을 위해 충분하지 않다고 표시
월터 Vehoeven에게

1
10 명의 클라이언트에 대한 응답 시간을 28 초에서 1.5 초로 줄였습니다 !!! 대박!
JeffS

17

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)
{
    ...
}

이것은 올바른 완전 비동기 구현입니다.
Tim P.

1
지연을 제공하기 위해 Task.Delay를 사용하는 CancellationTokenSource/TaskCompletionSource대신를 사용하여 특정 시간 후에 완료되는 작업을 생성 할 수없는 이유는 무엇 입니까? (내가 시도했지만
Daniel

작업은 언제 취소됩니까? 확실히 이것은 시간 초과 후 호출 된 차단을 해제하지만 ConnectAsync ()가 여전히 어딘가에서 스레드 풀에서 실행되고 있지 않습니까?
TheColonel26

나는 또한 @MondKin의 질문에 대한 답을 알고 싶습니다
TheColonel26

9

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

정교한 코드에 감사드립니다. 시간 초과 전에 연결 호출이 실패하면 SocketException이 throw 될 수 있습니까?
Macke 2015

이미해야합니다. WaitOne은 Connect 호출이 완료되거나 (성공적이거나 그렇지 않은 경우) 시간 초과가 경과하면 해제됩니다. ! client.Connected에 대한 검사는 연결이 "빠르게 실패"하는 경우 예외를 발생시킵니다.
Adster 2015

8

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

3

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

1

다음은 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)
{
    ...
}

1

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.

자세한 정보 및 기타 예제 는 Task.Wait MSDN 문서 를 참조하십시오 .


1

으로 사이먼 Mourier이 언급이 사용 가능 ConnectAsync으로하여 TcpClient의 방법을 Task가능한 한 빨리 추가 및 정지 동작에서.
예를 들면 :

// ...
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 of 1 second
{

    // ... transfer

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

또한 다음 과 같은 몇 가지 예제를 사용 하여 AsyncTcpClient C # 라이브러리를 확인하는 것이 좋습니다.Server <> Client .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.