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

C# 同步 Socket 程式設計完整教學:Client/Server 實作範例

     編輯:關於C語言

前言

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 關閉連線與釋放資源 否

同步 TCP Client 完整實作

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();
        }
    }
}

同步 TCP Server 完整實作

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();
        }
    }
}

關鍵技術細節深度解析

1. 訊息邊界問題與解決方案

TCP 是串流協定,不保証訊息邊界。一次 Send 可能被分割成多次 Receive,反之亦然。常見解決方案:

  • 長度前綴:前 4 位元組傳送訊息長度 (二進位)
  • 分隔符號:如範例的 <EOF> (需確保資料不含此標記)
  • 固定長度:所有訊息長度相同
  • 應用層協定:HTTP、WebSocket、Protobuf 等

2. 部分傳送/接收處理

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;
}

3. 正確關閉 Socket 的步驟

  1. Shutdown(SocketShutdown.Both):發送 FIN 封包,通知對方不再傳送/接收
  2. Close() 或 Dispose():釋放非受控資源 (Handle)
  3. 建議使用 using 區塊確保釋放

4. 同步模型的限制與適用場景

優點 缺點
程式碼直觀,邏輯線性易讀 單一執行緒一次只能處理一個連線
除錯容易,堆疊追蹤清晰 高併發下需大量執行緒,資源消耗大
適合簡單工具、測試程式 UI 應用會凍結介面 (需另開執行緒)

進階改良:支援多客戶端 (Thread Per Client)

// 在 Server 主迴圈中
while (true)
{
    Socket handler = listener.Accept();
    // 為每個客戶端啟動新執行緒
    Thread clientThread = new Thread(() => HandleClient(handler));
    clientThread.IsBackground = true; // 隨主程式結束
    clientThread.Start();
}
⚠ 注意: "Thread Per Client" 模式在連線數超過數百時會耗盡執行緒池。生產環境高併發請使用 非同步 Socket (APM/EAP/Task-based) 或 IOCP 模型。

總結

同步 Socket 程式設計雖然概念簡單,但正確處理「部分傳送/接收」、「訊息邊界」、「資源釋放」等細節至關重要。本文提供的 Client/Server 完整範例展示了標準 TCP 通訊流程。對於學習網路程式設計、建立簡單工具或低併發內部系統,同步模型是很好的起點;若需支援高併發,請進階學習 async/await 配合 SocketAsyncEventArgs 或 System.Net.Sockets.Socket 的 Task-based 非同步方法。

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