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.