純 OO 設計會建立抽象基類 MessageBase,每個訊息類別繼承並覆寫虛擬方法 HandleMessage()。但這帶來三大痛點:
Activator.CreateInstance 遠慢於直接 new。本系列採用 Switch 模式 + 介面序列化,犧牲極少 OO 純度,換取極大的開發效率與執行效能。
單一埠口無法同時接受多個用戶端長連線。解決方案:主埠 (9999) 僅負責「握手分配」,實際通訊轉移至動態分配的子埠。
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
public class SocketListener
{
private readonly int _basePort;
private readonly Thread _listenThread;
private readonly IPAddress _localAddr = IPAddress.Parse("127.0.0.1");
public SocketListener(int port)
{
_basePort = port;
_listenThread = new Thread(WaitForConnection) { IsBackground = true };
_listenThread.Start();
}
private void WaitForConnection()
{
TcpListener listener = new TcpListener(_localAddr, _basePort);
listener.Start();
Console.WriteLine($"主監聽器已在埠 {_basePort} 啟動。");
while (true)
{
Console.WriteLine("等待初始連線請求...");
using (Socket handshakeSocket = listener.AcceptSocket())
using (NetworkStream stream = new NetworkStream(handshakeSocket))
using (BinaryReader reader = new BinaryReader(stream))
using (BinaryWriter writer = new BinaryWriter(stream))
{
Console.WriteLine("收到握手請求。");
// 尋找可用子埠
int clientPort = _basePort + 1;
TcpListener clientListener = null;
while (true)
{
try
{
clientListener = new TcpListener(_localAddr, clientPort);
clientListener.Start();
break; // 成功佔用埠口
}
catch (SocketException)
{
clientPort++; // 埠口被佔用,嘗試下一個
}
}
// 啟動專屬服務執行緒
SocketServer server = new SocketServer(clientListener);
// 回傳子埠號給用戶端
writer.Write(clientPort);
writer.Flush();
Console.WriteLine($"已分配子埠 {clientPort} 給用戶端。");
}
}
}
}
public class SocketServer
{
private readonly TcpListener _listener;
private readonly Thread _serverThread;
public SocketServer(TcpListener listener)
{
_listener = listener;
_serverThread = new Thread(ProcessClient) { IsBackground = true };
_serverThread.Start();
}
private void ProcessClient()
{
Console.WriteLine($"子伺服器在埠 {((IPEndPoint)_listener.LocalEndpoint).Port} 等待用戶端連線...");
using (TcpClient client = _listener.AcceptTcpClient())
using (NetworkStream stream = client.GetStream())
using (BinaryReader reader = new BinaryReader(stream))
using (BinaryWriter writer = new BinaryWriter(stream))
{
Console.WriteLine("用戶端已連線至子埠,開始處理訊息迴圈。");
try
{
while (true)
{
MessageType msgType = (MessageType)reader.ReadInt32();
switch (msgType)
{
case MessageType.RequestEmployee:
// 模擬資料庫查詢
Employee emp = new Employee("Eric Gunnerson", "One Microsoft Way");
emp.Serialize(writer);
writer.Flush();
break;
default:
Console.WriteLine($"忽略未知訊息:{msgType}");
break;
}
}
}
catch (IOException)
{
Console.WriteLine("用戶端斷開連線。");
}
catch (Exception ex)
{
Console.WriteLine($"處理錯誤:{ex.Message}");
}
finally
{
_listener.Stop(); // 釋放子埠
}
}
}
}
WinForms/WPF 應用需在 UI 執行緒更新介面,Socket 接收執行緒不能直接操作 UI 控制項。解決方案:委派事件 + Control.Invoke。
using System;
using System.Net.Sockets;
using System.IO;
using System.Windows.Forms;
public class SocketClient
{
private readonly TcpClient _client;
private readonly BinaryReader _reader;
private readonly BinaryWriter _writer;
private readonly Thread _receiveThread;
private readonly Form _uiForm;
// 定義事件
public event Action EmployeeReceived;
public event Action CurrentCountReceived;
public SocketClient(string host, int port, Form uiForm)
{
_uiForm = uiForm;
_client = new TcpClient(host, port);
NetworkStream stream = _client.GetStream();
_reader = new BinaryReader(stream);
_writer = new BinaryWriter(stream);
// 先連主埠取得子埠
int subPort = _reader.ReadInt32();
_reader.Close(); _writer.Close(); stream.Close(); _client.Close();
// 連接子埠
_client = new TcpClient(host, subPort);
stream = _client.GetStream();
_reader = new BinaryReader(stream);
_writer = new BinaryWriter(stream);
_receiveThread = new Thread(ReceiveLoop) { IsBackground = true };
_receiveThread.Start();
}
public void RequestEmployee(int id)
{
RequestEmployee req = new RequestEmployee(id);
req.Serialize(_writer);
_writer.Flush();
}
private void ReceiveLoop()
{
try
{
while (true)
{
MessageType msgType = (MessageType)_reader.ReadInt32();
switch (msgType)
{
case MessageType.Employee:
Employee emp = new Employee(_reader);
// 跨執行緒安全觸發事件
_uiForm.Invoke(new Action(() => EmployeeReceived?.Invoke(emp)));
break;
case MessageType.CurrentCount:
int count = _reader.ReadInt32();
_uiForm.Invoke(new Action(() => CurrentCountReceived?.Invoke(count)));
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"接收執行緒異常:{ex.Message}");
}
}
}
public partial class MainForm : Form
{
private SocketClient _client;
public MainForm()
{
InitializeComponent();
_client = new SocketClient("127.0.0.1", 9999, this);
_client.EmployeeReceived += OnEmployeeReceived;
_client.CurrentCountReceived += OnCountReceived;
}
private void btnRequest_Click(object sender, EventArgs e)
{
if (int.TryParse(txtId.Text, out int id))
_client.RequestEmployee(id);
}
private void OnEmployeeReceived(Employee emp)
{
// 此處已在 UI 執行緒,可直接操控控制項
lblName.Text = emp.Name;
lblAddress.Text = emp.Address;
}
private void OnCountReceived(int count)
{
lblCount.Text = count.ToString();
}
}
System.Net.Sockets。CurrentCount 每秒廣播) 自然實現,無需輪詢。MessageType、實作 ISocketMessage、補充 Switch case。async/await + ReadAsync 取代專用接收執行緒,降低執行緒佔用。SslStream) 保護資料機密性。