TCP (Transmission Control Protocol) 提供可靠、有序、雙向的位元組流傳輸。.NET 在 System.Net.Sockets 命名空間提供兩個高層封裝:
兩者內部皆封裝 Socket,透過 NetworkStream 進行資料流讀寫,支援標準 .NET Stream 操作。
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();
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();
}
| 方法 | 適用場景 | 注意事項 |
|---|---|---|
Read/Write | 同步封鎖 | 需自行處理部份讀寫、迴圈直到完成 |
ReadAsync/WriteAsync | 非同步、高併發 | 配合 CancellationToken 控制逾時 |
StreamReader/Writer | 文字協議、行導向 | 內部緩衝,ReadLineAsync 便利 |
BinaryReader/Writer | 二進位協議、固定長度 | 處理 Endian、結構化資料 |
// 方案 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
TcpClient, NetworkStream 正確 Disposeclient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true)client.NoDelay = true (即時互動/遊戲)client.ReceiveBufferSize, SendBufferSize 依據 MTU/訊息大小調整
// 傳統同步:每連線佔用一執行緒 → 1萬連線 = 1萬執行緒 (記憶體爆炸)
// 非同步 I/O (IOCP):
// - 發起非同步操作 → 釋放執行緒回執行緒池
// - 核心完成 I/O → 佇列完成通知 → 執行緒池執行 continuation
// - 少量執行緒可處理萬級連線 (C10K 問題解決)
SslStream 包裝 NetworkStream,支援憑證驗證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 層邏輯。