Socket 是網路程式設計的基石。本文系統整理 C# 中同步/非同步 Socket、TcpListener/TcpClient 高階封裝、多線程模型、以及核心網路概念(協議、端點、位元組順序、流控等),附上可直接落地的伺服器/客戶端完整範本。
| 概念 | 說明 |
|---|---|
| Socket | 網路通訊端點,封裝 IP + Port + 協議 |
| AddressFamily.InterNetwork | IPv4 |
| SocketType.Stream | TCP,可靠、有序、雙向位元組流 |
| SocketType.Dgram | UDP,不可靠、無序、資料報 |
| IPEndPoint | IP 位址 + 埠號的組合 |
| 協議端口 | 傳輸層識別應用程式進程的邏輯地址 |
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class SyncTcpServer
{
private readonly Socket _listenSocket;
private readonly Thread _listenThread;
private bool _running;
public SyncTcpServer(int port)
{
_listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_listenSocket.Bind(new IPEndPoint(IPAddress.Any, port));
_listenSocket.Listen(100); // backlog
_listenThread = new Thread(ListenLoop) { IsBackground = true };
}
public void Start() => _listenThread.Start();
private void ListenLoop()
{
_running = true;
while (_running)
{
try
{
Socket client = _listenSocket.Accept(); // 阻塞等待連線
ThreadPool.QueueUserWorkItem(HandleClient, client); // 交給執行緒池處理
}
catch (SocketException) when (!_running) { break; }
}
}
private void HandleClient(object state)
{
var client = (Socket)state;
var buffer = new byte[4096];
try
{
while (true)
{
int len = client.Receive(buffer);
if (len == 0) break; // 客戶端正常關閉
string msg = Encoding.UTF8.GetString(buffer, 0, len);
Console.WriteLine($"收到:{msg}");
// 回應
client.Send(Encoding.UTF8.GetBytes($"Echo: {msg}"));
}
}
catch (SocketException) { /* 連線中斷 */ }
finally { client.Close(); }
}
public void Stop()
{
_running = false;
_listenSocket.Close();
}
}
public class SyncTcpClient
{
private readonly Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
public void Connect(string ip, int port) => _socket.Connect(ip, port);
public void Send(string msg) => _socket.Send(Encoding.UTF8.GetBytes(msg));
public string Receive()
{
var buffer = new byte[4096];
int len = _socket.Receive(buffer);
return len > 0 ? Encoding.UTF8.GetString(buffer, 0, len) : null;
}
public void Close() => _socket.Close();
}
內建 NetworkStream 支援 StreamReader/Writer,程式碼更簡潔。
public class TcpServer
{
private readonly TcpListener _listener;
private readonly CancellationTokenSource _cts = new();
public TcpServer(int port) => _listener = new TcpListener(IPAddress.Any, port);
public async Task StartAsync()
{
_listener.Start();
while (!_cts.Token.IsCancellationRequested)
{
TcpClient client = await _listener.AcceptTcpClientAsync();
_ = Task.Run(() => HandleClientAsync(client), _cts.Token); // Fire-and-forget
}
}
private async Task HandleClientAsync(TcpClient client)
{
using (client)
using (var stream = client.GetStream())
using (var reader = new StreamReader(stream, Encoding.UTF8))
using (var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true })
{
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
Console.WriteLine($"收到:{line}");
await writer.WriteLineAsync($"Echo: {line}");
}
}
}
public void Stop() => _cts.Cancel();
}
現代 .NET 僅建議使用 **TAP (Task-based Asynchronous Pattern)**,即 AcceptAsync、ReceiveAsync、SendAsync 或搭配 NetworkStream 的 ReadAsync/WriteAsync。舊式 BeginAccept/EndAccept、SocketAsyncEventArgs 僅供維護舊專案參考。
// 物件池避免頻繁分配 SocketAsyncEventArgs
// 參考 Microsoft 官方範例:https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Sockets/tests/SocketAsyncEventArgsTests.cs
| 模型 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| 每連線一執行緒 | 低併發 (<100) | 程式碼簡單、阻塞式 API 直觀 | 執行緒開銷大、記憶體壓力高 |
| 執行緒池 + 同步 Socket | 中併發 (100~1,000) | 利用 ThreadPool 自動調度 | 阻塞呼叫佔用池執行緒,可能導致饑餓 |
| 非同步 I/O (TAP) + 完成端口 | 高併發 (>10,000) | 少量執行緒處理大量連線、擴展性最佳 | 程式碼複雜、錯誤處理繁瑣 |
| SocketAsyncEventArgs | 極高併發、極低延遲 | 零分配、完成端口原生支援 | API 困難、易出 Bug |
int/long 需用 IPAddress.HostToNetworkOrder / NetworkToHostOrder。\n, \r\n)socket.NoDelay = true。socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true)。socket.Shutdown(SocketShutdown.Both) 後再 Close(),確保緩衝區資料送達。SocketException.SocketErrorCode 區分 ConnectionReset、TimedOut、WouldBlock 等。
class ChatServer
{
private readonly TcpListener _listener = new TcpListener(IPAddress.Any, 8888);
private readonly List _clients = new();
private readonly object _lock = new();
public async Task RunAsync()
{
_listener.Start();
Console.WriteLine("伺服器啟動於 8888...");
while (true)
{
TcpClient client = await _listener.AcceptTcpClientAsync();
_ = HandleClientAsync(client);
}
}
private async Task HandleClientAsync(TcpClient client)
{
using (client)
using (var stream = client.GetStream())
using (var reader = new StreamReader(stream, Encoding.UTF8))
using (var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true })
{
lock (_lock) _clients.Add(writer);
try
{
string msg;
while ((msg = await reader.ReadLineAsync()) != null)
{
Broadcast(msg, writer);
}
}
finally
{
lock (_lock) _clients.Remove(writer);
}
}
}
private void Broadcast(string msg, StreamWriter sender)
{
lock (_lock)
{
foreach (var w in _clients)
{
if (w != sender) w.WriteLine(msg);
}
}
}
}
選擇正確的 Socket 程式設計模型取決於併發規模與團隊熟悉度:
TcpListener/TcpClient + async/await + StreamReader/Writer。Socket + ReceiveAsync/SendAsync + Memory<byte> / ArrayPool。SocketAsyncEventArgs + 物件池 + IOCompletionPort 原生語意。無論何種模型,掌握「訊息幀處理」、「背壓控制」、「連線生命週期管理」才是打造穩健網路服務的核心能力。