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

C# 即時通訊技術:基於 Socket 的客戶端/伺服器架構與 XML 資料推播實作

     編輯:關於C#

前言:即時資料推播的經典實作模式

在 WebSocket、SignalR、gRPC Streaming 普及之前,TcpClient/TcpListener 搭配專用執行緒是構建即時雙向通訊的標準做法。本文完整展示一個金融報價即時推播系統,涵伺服器端多客戶端管理、客戶端非同步接收、ListView 跨執行緒 UI 更新、文字/Xml 雙格式協定解析、資料檔案載入推播等核心功能,為理解現代即時通訊底層機制提供珍貴參考。

系統架構與通訊流程

┌─────────────┐                    ┌─────────────┐
│  TcpServer  │                    │  TcpClient  │
│  (Port 8002)│                    │             │
│             │  1. Listen         │             │
│  ┌────────┐ │◄───────────────────┤             │
│  │Thread  │ │  2. Accept         │             │
│  │Pool    │ │                    │             │
│  └────┬───┘ │                    │             │
│       │    │  3. Spawn Thread   │  4. Connect │
│  ┌────▼────┐│◄───────────────────┤  (IP/Port)  │
│  │ClientMgr││                    │             │
│  │Hashtable││  5. Send Credentials              │
│  └────┬────┘│───────────────────►│             │
│       │    │  6. Spawn Recv Thread             │
│  ┌────▼────┐│◄───────────────────┤  ┌────────┐ │
│  │RecvLoop ││                    │  │RecvLoop│ │
│  └────┬────┘│  7. Push Data      │  └────┬───┘ │
│       │    │───────────────────►│       │     │
│  ┌────▼────┐│   (Text/Xml)       │  ┌────▼───┐ │
│  │ListView ││                    │  │ListView│ │
│  │Update   ││  8. UI Invoke      │  │Update  │ │
│  └─────────┘│◄───────────────────┤  └────────┘ │
└─────────────┘                    └─────────────┘

伺服器端核心實作

1. 啟動監聽與執行緒池管理

public partial class ServerForm : Form
{
    private TcpListener _listener;
    private Hashtable _socketTable = new Hashtable();  // id → Socket
    private Hashtable _threadTable = new Hashtable();  // id → Thread
    private Hashtable _userTable = new Hashtable();    // id → UserName
    private int _connectId = 0;
    private const int MaxConnected = 100;

    public ServerForm()
    {
        InitializeComponent();
        _listener = new TcpListener(8002);
        _listener.Start();
        lblStatus.Text = $"Listen at: {_listener.LocalEndpoint}";

        Thread acceptThread = new Thread(WaitingForClient);
        acceptThread.Start();
    }

    private void WaitingForClient()
    {
        while (true)
        {
            Socket clientSocket = _listener.AcceptSocket();  // 阻塞等待
            int id = Interlocked.Increment(ref _connectId) % 10000;

            if (_socketTable.Count < MaxConnected)
            {
                lock (_socketTable)
                {
                    while (_socketTable.ContainsKey(id)) 
                        id = Interlocked.Increment(ref _connectId) % 10000;

                    Thread worker = new Thread(ReadSocket);
                    _socketTable[id] = clientSocket;
                    _threadTable[id] = worker;
                    worker.Start();
                }
            }
        }
    }

2. 通訊協定解析:登入驗證與資料推播

private void ReadSocket()
{
    long id = _connectId;  // 捕獲當前 ID
    Socket socket = (Socket)_socketTable[id];
    int listViewIndex = -1;

    while (true)
    {
        if (!socket.Connected) break;

        byte[] buffer = new byte[37];  // 固定長度登入封包
        try
        {
            int received = socket.Receive(buffer, buffer.Length, SocketFlags.None);
            if (received <= 0) break;

            string text = Encoding.ASCII.GetString(buffer, 0, received);
            string[] parts = text.Split(':');
            string userName = parts[0];

            int authCode = CheckUserInfo(userName);  // 1=已存在, 2=新用戶
            if (authCode == 2)
            {
                _userTable[id] = userName;
                // 跨執行緒更新 ListView
                ListViewItem item = new ListViewItem(userName);
                item.SubItems.Add(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));
                item.ImageIndex = 0;  // 綠燈
                Invoke(() => 
                {
                    listView2.Items.Add(item);
                    listViewIndex = listView2.Items.IndexOf(item);
                });
            }
            else if (authCode == 1)
            {
                // 用戶已登入,拒絕或踢除舊連線
            }
        }
        catch (Exception)
        {
            if (!socket.Connected) break;
        }
    }
    CloseConnection(id, listViewIndex);
}

3. 資料載入與廣播推播

private void LoadData_Click(object sender, EventArgs e)
{
    Thread loadThread = new Thread(LoadThread);
    loadThread.Start();
}

private void LoadThread()
{
    string[] lines = File.ReadAllLines("Issue.txt");
    foreach (string line in lines)
    {
        if (string.IsNullOrEmpty(line)) break;

        // 解析固定寬度欄位
        IssueData data = new IssueData
        {
            Symbol = line.Substring(0, 4),
            Bid = line.Substring(4, 5),
            Offer = line.Substring(9, 5),
            Volume = line.Substring(16)
        };

        // 組裝封包:\v 開頭 \n 結尾
        string packet = $"\v{line}\n";
        BroadcastToAllClients(packet);
        
        // 等待 UI 更新完成 (生產者-消費者模式)
        _jobDone.WaitOne();  
    }
}

private void BroadcastToAllClients(string data)
{
    byte[] bytes = Encoding.ASCII.GetBytes(data);
    lock (_socketTable)
    {
        foreach (Socket s in _socketTable.Values)
        {
            if (s.Connected) s.Send(bytes, bytes.Length, SocketFlags.None);
        }
    }
}

客戶端核心實作

1. 非同步接收迴圈與協定解析

private void StartReceive()
{
    _receiveThread = new Thread(() =>
    {
        byte[] buffer = new byte[1];
        StringBuilder sb = new StringBuilder();
        byte firstByte = 0;
        int charCount = 0;

        while (true)
        {
            try
            {
                int read = _socket.Receive(buffer, 1, SocketFlags.None);
                if (read <= 0) break;

                byte b = buffer[0];
                if (charCount == 0) firstByte = b;

                switch (b)
                {
                    case 11:  // VT (Vertical Tab) - 封包開始
                        charCount = 0;
                        sb.Clear();
                        break;
                    case 10:  // LF (Line Feed) - 封包結束
                        charCount = 0;
                        string complete = sb.ToString();
                        if (firstByte == ':') HandleCommand(complete);
                        else if (firstByte == '<') HandleXml(complete);
                        else HandleText(complete);
                        sb.Clear();
                        break;
                    default:
                        sb.Append((char)b);
                        charCount++;
                        break;
                }
            }
            catch (Exception)
            {
                if (!_socket.Connected) break;
            }
        }
    });
    _receiveThread.Start();
}

2. 跨執行緒 UI 更新:MethodInvoker + BeginInvoke

private void UpdateListView()
{
    int index = FindItemIndex(_currentData.Symbol);
    Color highlight = Color.FromArgb(128, 0, 0);   // 深紅高亮
    Color normal = Color.FromArgb(255, 255, 175);  // 淺黃正常
    Color altRow = Color.FromArgb(248, 248, 248);  // 交替行

    if (index == -1)  // 新增
    {
        ListViewItem item = new ListViewItem(_currentData.Symbol);
        item.SubItems.AddRange(new[] { _currentData.Bid, _currentData.Offer, _currentData.Volume });
        listView1.Items.Add(item);
        index = listView1.Items.IndexOf(item);
        SetRowColor(index, index % 2 == 1 ? normal : altRow);
    }
    else  // 更新
    {
        listView1.Items[index].SubItems[1].Text = _currentData.Bid;
        listView1.Items[index].SubItems[2].Text = _currentData.Offer;
        listView1.Items[index].SubItems[3].Text = _currentData.Volume;
    }

    // 閃爍效果:高亮 300ms → 恢復正常
    SetCellColor(index, 0, highlight);
    SetCellColor(index, 1, highlight);
    listView1.Update();
    Thread.Sleep(300);
    SetCellColor(index, 0, index % 2 == 1 ? normal : altRow);
    SetCellColor(index, 1, index % 2 == 1 ? normal : altRow);
    
    _jobDone.Set();  // 通知生產者繼續
}

// 呼叫方式
Invoke(new MethodInvoker(UpdateListView));

3. XML 解析與動態 ListView 更新

private void HandleXml(string xmlContent)
{
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xmlContent);
    
    foreach (XmlNode node in doc.SelectNodes("//Issue"))
    {
        _currentData = new IssueData
        {
            Symbol = node.SelectSingleNode("Symbol").InnerText,
            Bid = node.SelectSingleNode("Bid").InnerText,
            Offer = node.SelectSingleNode("Offer").InnerText,
            Volume = node.SelectSingleNode("Volume").InnerText
        };
        Invoke(new MethodInvoker(UpdateListView));
        _jobDone.WaitOne();
    }
}

伺服器端 XML 產生與推播

private void LoadXmlThread()
{
    XmlTextReader reader = new XmlTextReader("issue.xml");
    StringBuilder xmlBuilder = new StringBuilder();
    xmlBuilder.Append("\v");

    int recordFlag = -1, textCount = 0;
    string currentElement = "";

    while (reader.Read())
    {
        switch (reader.NodeType)
        {
            case XmlNodeType.Element:
                if (reader.Name == "Issue")
                {
                    recordFlag++;
                    if (recordFlag > 0)  // 發送上一筆完整 XML
                    {
                        BroadcastToAllClients(xmlBuilder.ToString());
                        xmlBuilder.Clear();
                        xmlBuilder.Append("\v");
                        Invoke(new MethodInvoker(UpdateListView));
                        _jobDone.WaitOne();
                    }
                    currentElement = reader.Name;
                    xmlBuilder.Append($"<{reader.Name}>");
                }
                else if (recordFlag >= 0)
                {
                    currentElement = reader.Name;
                    xmlBuilder.Append($"<{reader.Name}>");
                }
                break;

            case XmlNodeType.Text:
                textCount++;
                xmlBuilder.Append(reader.Value);
                xmlBuilder.Append($"");
                
                // 同步更新本地資料物件
                switch (textCount)
                {
                    case 1: _currentData.Symbol = reader.Value; break;
                    case 2: _currentData.Bid = reader.Value; break;
                    case 3: _currentData.Offer = reader.Value; break;
                    case 4: _currentData.Volume = reader.Value; break;
                }
                break;
        }
    }
}

關鍵技術總結與現代化對照

傳統 Socket 技術現代替代方案核心優勢
手動管理 Thread/ThreadPoolasync/await + ValueTask + Pipe無執行緒阻塞、高擴展性
自訂二進制/文字協定Protobuf / MessagePack / JSON標準化、版本容忍、跨語言
Control.Invoke/BeginInvokeDispatcher.Invoke / SynchronizationContext統一 UI 執行緒調度模型
Hashtable + lock 同步ConcurrentDictionary / Channel<T>無鎖高效能、背壓支援
固定封包長度/分隔符Length-Prefixed / WebSocket Frame邊界清晰、支援二進制
XmlTextReader (DOM/SAX)System.Text.Json / Utf8JsonReader高效能、零分配、強型別

部署與執行步驟

  1. 機器 A 執行 TcpServer.exe
  2. 機器 A/B 執行一或多個 TcpClient.exe
  3. 客戶端選單「連線」→ 輸入 Server IP:8002、用戶名、密碼
  4. Server ListView 顯示客戶端上線 → 點選「Load Data」或「Load Xml」
  5. 雙端即時顯示報價資料,ListView 閃爍高亮提示更新

注意:確保 Issue.txt、Issue.xml、MaskedTextBox.dll 與執行檔同目錄。

總結

本系統雖基於 .NET 1.1 時代技術,但完整展示了「專用執行緒接收、協定狀態機解析、跨執行緒 UI 同步、生產者-消費者流控、XML 即時序列化推播」等即時通訊系統的核心模式。這些模式在今天的 WebSocket Server、SignalR Hub、gRPC Streaming 實作中依然清晰可見。理解 Socket 層面的細節,能讓開發者在排查高階框架疑難雜症、設計自訂傳輸協定、優化延遲敏感系統時具備底層視角。

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