Socket 是網路通訊的基礎建構塊。同步 Socket 以阻塞方式運作:呼叫 Connect、Send、Receive、Accept 等方法時,執行緒會暫停直到作業完成。雖然較非同步模型簡單,但適用於低併發、教學示範或簡單工具程式。本文提供完整的同步 TCP Client/Server 實作,並解析關鍵細節與最佳實務。
| 類別/方法 | 用途 | 阻塞行為 |
|---|---|---|
| Socket Constructor | 建立 Socket 實例 (AddressFamily, SocketType, ProtocolType) | 否 |
| Connect / BeginConnect | Client 端發起連線 | 是 / 否 |
| Bind / Listen | Server 端綁定端點並開始監聽 | 否 |
| Accept | Server 端接受連入連線,回傳新 Socket | 是 |
| Send / Receive | 資料傳送與接收 | 是 |
| Shutdown / Close | 關閉連線與釋放資源 | 否 |
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SyncSocketClient
{
public class SynchronousClient
{
private const int Port = 11000;
private const string ServerHost = "localhost"; // 或指定 IP
private const int BufferSize = 1024;
private static readonly byte[] Buffer = new byte[BufferSize];
public static void StartClient()
{
try
{
// 1. 解析伺服器端點
IPHostEntry hostEntry = Dns.GetHostEntry(ServerHost);
IPAddress ipAddress = hostEntry.AddressList[0]; // 取第一個 IPv4/IPv6
IPEndPoint remoteEP = new IPEndPoint(ipAddress, Port);
// 2. 建立 TCP Socket
using Socket sender = new Socket(
ipAddress.AddressFamily,
SocketType.Stream,
ProtocolType.Tcp);
// 3. 連線至伺服器 (阻塞)
Console.WriteLine($"正在連線至 {remoteEP}...");
sender.Connect(remoteEP);
Console.WriteLine($"已連線:{sender.RemoteEndPoint}");
// 4. 準備發送資料 (以 為訊息結尾標記)
string message = "這是同步 Socket 測試訊息";
byte[] msgBytes = Encoding.UTF8.GetBytes(message); // 建議使用 UTF-8
// 5. 發送資料 (阻塞,可能需多次呼叫送完)
int totalSent = 0;
while (totalSent < msgBytes.Length)
{
int sent = sender.Send(msgBytes, totalSent, msgBytes.Length - totalSent, SocketFlags.None);
if (sent == 0) throw new SocketException((int)SocketError.ConnectionReset);
totalSent += sent;
}
Console.WriteLine($"已發送 {totalSent} 位元組");
// 6. 接收回應 (阻塞,直到收到 或連線關閉)
StringBuilder receivedData = new StringBuilder();
while (true)
{
int bytesRead = sender.Receive(Buffer, 0, Buffer.Length, SocketFlags.None);
if (bytesRead == 0) break; // 對方關閉連線
receivedData.Append(Encoding.UTF8.GetString(Buffer, 0, bytesRead));
// 檢查結尾標記
if (receivedData.ToString().Contains(""))
break;
}
Console.WriteLine($"收到回應:{receivedData}");
// 7. 正確關閉連線
sender.Shutdown(SocketShutdown.Both);
}
catch (SocketException se)
{
Console.WriteLine($"Socket 錯誤 [{se.SocketErrorCode}]: {se.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"錯誤: {ex.Message}");
}
}
public static void Main()
{
StartClient();
Console.WriteLine("按任意鍵結束...");
Console.ReadKey();
}
}
}
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SyncSocketServer
{
public class SynchronousServer
{
private const int Port = 11000;
private const int Backlog = 10; // 等待佇列長度
private const int BufferSize = 1024;
public static void StartServer()
{
// 1. 建立本機端點 (監聽所有介面)
IPEndPoint localEP = new IPEndPoint(IPAddress.Any, Port);
// 2. 建立監聽 Socket
using Socket listener = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
try
{
// 3. 綁定並開始監聽
listener.Bind(localEP);
listener.Listen(Backlog);
Console.WriteLine($"伺服器啟動,監聽 {localEP}...");
while (true) // 主迴圈:持續接受新連線
{
Console.WriteLine("等待客戶端連線...");
// 4. 接受連線 (阻塞直到有客戶端連入)
using Socket handler = listener.Accept();
Console.WriteLine($"客戶端已連線:{handler.RemoteEndPoint}");
// 5. 處理單一客戶端通訊 (同步阻塞,無法同時處理多客戶端)
HandleClient(handler);
Console.WriteLine("客戶端斷線,等待下一個連線...");
}
}
catch (Exception ex)
{
Console.WriteLine($"伺服器錯誤: {ex.Message}");
}
}
private static void HandleClient(Socket handler)
{
byte[] buffer = new byte[BufferSize];
StringBuilder receivedData = new StringBuilder();
try
{
// 接收資料迴圈
while (true)
{
int bytesRead = handler.Receive(buffer, 0, buffer.Length, SocketFlags.None);
if (bytesRead == 0) break; // 客戶端主動斷線
receivedData.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
// 檢查訊息結尾標記
if (receivedData.ToString().Contains(""))
break;
}
string message = receivedData.ToString();
Console.WriteLine($"收到訊息:{message}");
// 6. 回傳相同資料
byte[] responseBytes = Encoding.UTF8.GetBytes(message);
int totalSent = 0;
while (totalSent < responseBytes.Length)
{
int sent = handler.Send(responseBytes, totalSent, responseBytes.Length - totalSent, SocketFlags.None);
if (sent == 0) throw new SocketException((int)SocketError.ConnectionReset);
totalSent += sent;
}
Console.WriteLine($"已回傳 {totalSent} 位元組");
}
catch (Exception ex)
{
Console.WriteLine($"處理客戶端錯誤: {ex.Message}");
}
finally
{
// 7. 確保資源釋放
try { handler.Shutdown(SocketShutdown.Both); } catch { }
handler.Close();
}
}
public static void Main()
{
StartServer();
}
}
}
TCP 是串流協定,不保証訊息邊界。一次 Send 可能被分割成多次 Receive,反之亦然。常見解決方案:
<EOF> (需確保資料不含此標記)Send/Receive 回傳值為實際處理的位元組數,可能小於請求長度。必須使用迴圈確保完整傳輸:
int totalSent = 0;
while (totalSent < data.Length)
{
int sent = socket.Send(data, totalSent, data.Length - totalSent, SocketFlags.None);
if (sent == 0) throw new Exception("連線已中斷");
totalSent += sent;
}
Shutdown(SocketShutdown.Both):發送 FIN 封包,通知對方不再傳送/接收Close() 或 Dispose():釋放非受控資源 (Handle)using 區塊確保釋放| 優點 | 缺點 |
|---|---|
| 程式碼直觀,邏輯線性易讀 | 單一執行緒一次只能處理一個連線 |
| 除錯容易,堆疊追蹤清晰 | 高併發下需大量執行緒,資源消耗大 |
| 適合簡單工具、測試程式 | UI 應用會凍結介面 (需另開執行緒) |
// 在 Server 主迴圈中
while (true)
{
Socket handler = listener.Accept();
// 為每個客戶端啟動新執行緒
Thread clientThread = new Thread(() => HandleClient(handler));
clientThread.IsBackground = true; // 隨主程式結束
clientThread.Start();
}
同步 Socket 程式設計雖然概念簡單,但正確處理「部分傳送/接收」、「訊息邊界」、「資源釋放」等細節至關重要。本文提供的 Client/Server 完整範例展示了標準 TCP 通訊流程。對於學習網路程式設計、建立簡單工具或低併發內部系統,同步模型是很好的起點;若需支援高併發,請進階學習 async/await 配合 SocketAsyncEventArgs 或 System.Net.Sockets.Socket 的 Task-based 非同步方法。