首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# TCP 網路程式設計完整指南:TcpListener/TcpClient 實作時間伺服器

     編輯:關於C語言

TCP 協議與 .NET 網路類別

TCP (Transmission Control Protocol) 提供可靠、有序、雙向的位元組流傳輸。.NET 在 System.Net.Sockets 命名空間提供兩個高層封裝:

  • TcpClient:客戶端,主動發起連線
  • TcpListener:伺服器端,監聽埠口接受連線

兩者內部皆封裝 Socket,透過 NetworkStream 進行資料流讀寫,支援標準 .NET Stream 操作。

時間伺服器完整實作 (RFC 868 簡化版)

伺服器端 — TcpListener


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

public class TimeServer
{
    private readonly TcpListener _listener;
    private readonly CancellationTokenSource _cts = new();

    public TimeServer(int port = 13) // 標準 Daytime Protocol 埠
    {
        _listener = new TcpListener(IPAddress.Any, port); // 監聽所有介面
    }

    public async Task StartAsync()
    {
        _listener.Start();
        Console.WriteLine($"時間伺服器啟動,監聽埠口 {((IPEndPoint)_listener.LocalEndpoint).Port}");

        try
        {
            while (!_cts.Token.IsCancellationRequested)
            {
                // 非同步接受連線,不阻塞執行緒
                TcpClient client = await _listener.AcceptTcpClientAsync(_cts.Token);
                _ = HandleClientAsync(client); // Fire-and-forget,或用 Task.Run 管理
            }
        }
        catch (OperationCanceledException) { }
        finally
        {
            _listener.Stop();
        }
    }

    public void Stop() => _cts.Cancel();

    private async Task HandleClientAsync(TcpClient client)
    {
        using (client)
        using (NetworkStream stream = client.GetStream())
        {
            try
            {
                // 取得當前時間 (UTC 格式)
                string time = DateTime.UtcNow.ToString("o"); // ISO 8601: 2024-03-15T14:30:05.1234567Z
                byte[] data = Encoding.ASCII.GetBytes(time + "\r\n");
                
                // 非同步寫入
                await stream.WriteAsync(data, 0, data.Length);
                Console.WriteLine($"[{client.Client.RemoteEndPoint}] 發送時間: {time}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"處理客戶端錯誤: {ex.Message}");
            }
        } // using 區塊自動關閉 stream 與 client
    }
}

// 使用方式
// var server = new TimeServer(13);
// await server.StartAsync();
// Console.ReadLine();
// server.Stop();

客戶端 — TcpClient


public class TimeClient
{
    public async Task GetTimeAsync(string host, int port = 13, int timeoutMs = 5000)
    {
        using var client = new TcpClient();
        using var cts = new CancellationTokenSource(timeoutMs);

        try
        {
            await client.ConnectAsync(host, port, cts.Token);
            using NetworkStream stream = client.GetStream();

            // 讀取回應 (Daytime 協議:伺服器發送後關閉連線)
            using var reader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true);
            string response = await reader.ReadToEndAsync();
            return response.Trim();
        }
        catch (OperationCanceledException)
        {
            throw new TimeoutException($"連線逾時 ({timeoutMs}ms)");
        }
        catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionRefused)
        {
            throw new InvalidOperationException("連線被拒絕,伺服器未啟動或埠口錯誤");
        }
    }
}

// 使用方式
// var client = new TimeClient();
// string time = await client.GetTimeAsync("127.0.0.1");
// Console.WriteLine($"伺服器時間: {time}");

同步版本 (教學/舊專案相容)


// 伺服器同步迴圈
public void StartSync()
{
    _listener.Start();
    while (true)
    {
        TcpClient client = _listener.AcceptTcpClient(); // 封鎖等待
        ThreadPool.QueueUserWorkItem(_ => HandleClientSync(client)); // 執行緒池處理
    }
}

private void HandleClientSync(TcpClient client)
{
    using (client)
    using (NetworkStream stream = client.GetStream())
    {
        string time = DateTime.Now.ToString();
        byte[] data = Encoding.ASCII.GetBytes(time);
        stream.Write(data, 0, data.Length);
    }
}

// 客戶端同步
public string GetTimeSync(string host, int port)
{
    using var client = new TcpClient(host, port); // 連線同步封鎖
    using var stream = client.GetStream();
    using var reader = new StreamReader(stream, Encoding.ASCII);
    return reader.ReadToEnd();
}

關鍵技術細節深度解析

1. NetworkStream 讀寫模式

方法適用場景注意事項
Read/Write同步封鎖需自行處理部份讀寫、迴圈直到完成
ReadAsync/WriteAsync非同步、高併發配合 CancellationToken 控制逾時
StreamReader/Writer文字協議、行導向內部緩衝,ReadLineAsync 便利
BinaryReader/Writer二進位協議、固定長度處理 Endian、結構化資料

2. 訊息邊界處理 (TCP 是位元組流,無訊息邊界)


// 方案 1:長度前綴 (4 bytes 長度 + 資料)
await stream.WriteAsync(BitConverter.GetBytes(data.Length));
await stream.WriteAsync(data);

// 讀取端
byte[] lenBuf = new byte[4];
await stream.ReadExactlyAsync(lenBuf); // .NET 7+ ReadExactlyAsync
int len = BitConverter.ToInt32(lenBuf);
byte[] data = new byte[len];
await stream.ReadExactlyAsync(data);

// 方案 2:分隔符 (如 \r\n, \0)
await stream.WriteAsync(Encoding.UTF8.GetBytes(message + "\n"));

// 讀取端
using var reader = new StreamReader(stream, Encoding.UTF8, leaveOpen: true);
string msg = await reader.ReadLineAsync();

// 方案 3:連線關閉當作結束 (HTTP 1.0, Daytime Protocol)
// 伺服器寫完即關閉,客戶端 ReadToEnd

3. 連線管理最佳實踐

  • using 區塊:確保 TcpClient, NetworkStream 正確 Dispose
  • Keep-Alive:長連線需啟用 client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true)
  • NoDelay:關閉 Nagle 演算法 client.NoDelay = true (即時互動/遊戲)
  • 緩衝區大小:client.ReceiveBufferSize, SendBufferSize 依據 MTU/訊息大小調整

4. 非同步模式效能優勢


// 傳統同步:每連線佔用一執行緒 → 1萬連線 = 1萬執行緒 (記憶體爆炸)
// 非同步 I/O (IOCP):
// - 發起非同步操作 → 釋放執行緒回執行緒池
// - 核心完成 I/O → 佇列完成通知 → 執行緒池執行 continuation
// - 少量執行緒可處理萬級連線 (C10K 問題解決)

進階擴充方向

  1. SSL/TLS 加密:SslStream 包裝 NetworkStream,支援憑證驗證
  2. 通訊協定框架:Protobuf + 長度前綴、MessagePack、gRPC (HTTP/2)
  3. 連線池/重試:Polly 重試策略、客戶端連線池管理
  4. 伺服器擴充:Kestrel (ASP.NET Core)、DotNetty、SignalR
  5. UDP 支援:UdpClient 用於廣播、即時串流、DNS

常見錯誤與排查

錯誤原因解決
AddressAlreadyInUse埠口被佔用/ TIME_WAIT設定 ReuseAddress、等待或換埠
ConnectionRefused伺服器未啟動/防火牆阻擋檢查服務、防火牆規則、雲端安全群組
SocketException: 10060連線逾時檢查 IP/埠、網路通達性、MTU/VPN 問題
資料不完整/亂序未處理訊息邊界/緩衝區殘留實作長度前綴或分隔符解析器
記憶體洩漏未 Dispose Stream/Client、事件未取消訂閱using 區塊、WeakEventManager、定期檢查

結語

掌握 TcpListener/TcpClient 與 NetworkStream 的非同步讀寫模式,是 .NET 網路程式設計的基石。從簡單的時間伺服器延伸至高效能微服務通訊、即時訊息推送、物聯網裝置管理,核心原理始終一致:**非同步 I/O + 訊息邊界處理 + 資源確定性釋放**。建議新專案直接採用 ASP.NET Core gRPC 或 SignalR 等高層框架,僅在極致效能或特殊協議需求時手寫 TCP 層邏輯。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved