萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> 程式語言綜合

C# Socket 連線逾時控制:以 ManualResetEvent 實作 TcpClient 非同步連線逾時機制

問題背景

.NET TcpClient.Connect / BeginConnect **內建無連線逾時參數**。預設作業系統 TCP 連線逾時長達 20~30 秒,伺服器離線或網路故障時,客戶端會長時間卡死,嚴重影響使用者體驗。SocketOptionName.SendTimeout 僅控制**資料發送**逾時,**不適用於連線建立階段**。

解決方案:非同步連線 + ManualResetEvent 等待訊號

核心邏輯:

  1. 呼叫 BeginConnect 發起非同步連線。
  2. 主執行緒呼叫 WaitHandle.WaitOne(timeout) 等待訊號。
  3. 回呼中 EndConnect 完成連線,設定成功旗標並 Set() 釋放等待。
  4. 逾時未收到訊號 → 關閉 Socket、拋 TimeoutException。

生產級完整實作

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace SocketTimeoutDemo
{
    /// 
    /// 支援連線逾時的 TcpClient 包裝器
    /// 
    public static class TcpClientEx
    {
        /// 
        /// 嘗試非同步連線,支援毫秒級逾時控制
        /// 
        /// 遠端端點
        /// 逾時毫秒數,-1 表示無限等待
        /// 已連線的 TcpClient
        /// 逾時
        /// 連線失敗
        public static TcpClient ConnectWithTimeout(IPEndPoint remoteEndPoint, int timeoutMs)
        {
            if (remoteEndPoint == null) throw new ArgumentNullException(nameof(remoteEndPoint));
            if (timeoutMs < -1) throw new ArgumentOutOfRangeException(nameof(timeoutMs));

            var client = new TcpClient();
            var asyncResult = client.BeginConnect(remoteEndPoint.Address, remoteEndPoint.Port, null, null);

            // 等待連線完成或逾時
            bool completed = asyncResult.AsyncWaitHandle.WaitOne(timeoutMs, exitContext: false);

            if (!completed)
            {
                // 逾時:關閉 Socket,釋放資源
                client.Close();
                throw new TimeoutException($"連線逾時({timeoutMs} ms):{remoteEndPoint}");
            }

            try
            {
                // 完成連線,若失敗會拋出 SocketException
                client.EndConnect(asyncResult);
                return client;
            }
            catch
            {
                client.Close();
                throw;
            }
        }

        /// 
        /// 簡易同步連線包裝(內部同樣使用非同步+逾時)
        /// 
        public static TcpClient Connect(string host, int port, int timeoutMs = 5000)
        {
            var addresses = Dns.GetHostAddresses(host);
            if (addresses.Length == 0) throw new SocketException((int)SocketError.HostNotFound);

            foreach (var ip in addresses)
            {
                try
                {
                    return ConnectWithTimeout(new IPEndPoint(ip, port), timeoutMs);
                }
                catch (SocketException)
                {
                    // 嘗試下一個 IP
                }
            }
            throw new SocketException((int)SocketError.ConnectionRefused);
        }
    }
}

使用範例

try
{
    // 連線 192.168.1.100:8080,最多等待 3 秒
    using (var client = TcpClientEx.Connect("192.168.1.100", 8080, 3000))
    using (var stream = client.GetStream())
    {
        Console.WriteLine("連線成功!");
        // 收發資料...
    }
}
catch (TimeoutException)
{
    Console.WriteLine("連線逾時,伺服器無回應或網路不通。");
}
catch (SocketException ex)
{
    Console.WriteLine($"Socket 錯誤:{ex.SocketErrorCode} - {ex.Message}");
}

原始碼深度解析(教學版 vs 生產版)

原文提供的教學版使用 static 欄位儲存狀態,**非執行緒安全**,多執行緒並發呼叫會互相覆蓋旗標與例外。生產版採用 **APM 模式原生 IAsyncResult.AsyncWaitHandle**,無共享狀態、天然執行緒安全、程式碼更簡潔。

維度教學版 (原文)生產版 (建議)
執行緒安全❌ static 欄位共享✅ 無共享狀態
程式碼複雜度手動管理 ManualResetEvent、旗標、例外利用內建 AsyncWaitHandle,極簡
資源釋放需手動 Closeusing 區塊自動釋放
多 IP 嘗試不支援支援 DNS 多 IP 輪嘗

現代替代方案:.NET Core / .NET 5+ ValueTask 與 CancellationToken

// .NET Core 2.1+ 推薦寫法
public static async ValueTask ConnectAsync(string host, int port, TimeSpan timeout, CancellationToken ct = default)
{
    var client = new TcpClient();
    try
    {
        using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
        {
            cts.CancelAfter(timeout);
            await client.ConnectAsync(host, port).WaitAsync(cts.Token);
        }
        return client;
    }
    catch
    {
        client.Dispose();
        throw;
    }
}
WaitAsync (.NET 6+) 直接支援逾時取消,無需手造 Task.WhenAny。

結語

Socket 連線逾時控制是網路程式健壯性的基石。**棄用同步 Connect、拒絕 static 共享狀態**,採用 BeginConnect + AsyncWaitHandle(.NET Framework)或 ConnectAsync + WaitAsync/CancellationToken(.NET Core/5+),即可實現精準、安全、可維護的逾時機制。

copyright © 萬盛學電腦網 all rights reserved