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

打造區域網路即時通訊軟體:C# 版 LAN QQ 核心架構解析

     編輯:關於C語言

前言:無伺服器架構的區域網聊天室

在區域網路環境下,若想快速建立一個輕量級的即時通訊工具,採用 UDP 廣播 與 Peer-to-Peer (P2P) 架構是極佳的選擇。本文將深入剖析一個以 C# 實作的「區域網 QQ」原始碼,展示如何在不需專屬伺服器的情況下,實現線上用戶偵測、文字訊息收發與新訊息提醒等核心功能。

核心設計理念

  • 去中心化: 所有客戶端運行同一執行檔,透過 UDP 廣播封包進行發現與通訊,無需獨立伺服器端程式。
  • 埠號固定: 預設監聽與發送埠號均為 2525,廣播位址為 255.255.255.255。
  • 協定簡單化: 以 & 字元分隔欄位,定義指令碼:0(上線/重整)、1(發言)、2(傳檔預留)、9(已讀確認)。

關鍵類別結構與欄位

public class Form1 : System.Windows.Forms.Form
{
    // 網路核心物件
    public UdpClient listen = new UdpClient(2525);   // 監聽用 UdpClient
    public IPEndPoint End;                           // 廣播端點
    public IPAddress groupAddress = IPAddress.Parse("255.255.255.255");
    public int groupPort = 2525;

    // 本機身分資訊
    public string MachineName;   // 主機名稱
    public string MachineIP;     // 本機 IP

    // 當前會話對象資訊
    public string romeName;      // 對方暱稱
    public string romeIP;        // 對方 IP
    public string romeCon;       // 對方訊息內容

    // UI 控制項宣告...
    private System.Windows.Forms.ListBox box;      // 線上用戶列表
    private System.Windows.Forms.TextBox t_rec;    // 接收訊息區
    private System.Windows.Forms.TextBox t_send;   // 發送訊息區
    private System.Windows.Forms.Button button1;   // 發送鈕
    private System.Windows.Forms.Button button3;   // 重整鈕
    // ... 其他控制項
}

啟動流程與執行緒模型

表單載入時 (Form1_Load) 執行三項關鍵動作:

  1. 初始化本機身分: 透過 Dns.GetHostByName 取得主機名稱與首個 IP 位址。
  2. 啟動監聽執行緒: 建立獨立執行緒呼叫 ListenPort(),持續接收 UDP 封包。
  3. 廣播上線封包: 發送格式為 0&IP&Name&IP 的封包,通知區網其他節點。
private void Form1_Load(object sender, System.EventArgs e)
{
    End = new IPEndPoint(groupAddress, groupPort);

    // 取得本機資訊
    IPHostEntry myentry = Dns.GetHostByName(Dns.GetHostName());
    IPAddress myaddress = new IPAddress(myentry.AddressList[0].Address);
    MachineName = Dns.GetHostName();
    MachineIP = myaddress.ToString();

    // 初始化列表標題
    box.Items.Add("IP                主機名");

    // 啟動監聽執行緒
    runing = true;
    Thread myThread = new Thread(new ThreadStart(this.ListenPort));
    myThread.Start();

    // 廣播上線
    string sends = "0&" + MachineIP + "&" + MachineName + "&" + MachineIP;
    SendPack(sends);
}

封包接收與解析邏輯

ListenPort() 在迴圈中呼叫 UdpClient.Receive 封鎖等待資料,收到後交由 checkMessage() 依指令碼分流處理:

public void ListenPort()
{
    IPEndPoint tempEnd = new IPEndPoint(IPAddress.Any, 2525);
    while (runing)
    {
        Application.DoEvents(); // 讓 UI 保持回應
        try
        {
            byte[] recb = listen.Receive(ref tempEnd);
            checkMessage(recb);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
            break;
        }
    }
    listen.Close();
    box.Items.Add("執行緒已經退出!");
    runing = false;
}

public void checkMessage(byte[] recbb)
{
    string recStr = Encoding.Default.GetString(recbb);
    string[] rec = recStr.Split('&');

    switch (rec[0])
    {
        case "0": // 重整 / 上線通知
            // 若是自己發的,忽略;否則回覆自身資訊並加入列表
            if (rec[3] == MachineIP)
            {
                if (box.FindString(rec[1] + "  " + rec[2]) <= 0)
                    box.Items.Add(rec[1] + "  " + rec[2]);
            }
            else if (rec[1] == rec[3]) // 來自他人的重整請求
            {
                string sendstr = "0&" + MachineIP + "&" + MachineName + "&" + rec[3];
                SendPack(sendstr);
                if (box.FindString(rec[1] + "  " + rec[2]) <= 0)
                    box.Items.Add(rec[1] + "  " + rec[2]);
            }
            break;

        case "1": // 一般聊天訊息
            if (rec[3] == MachineIP) // 確認是發給自己的
            {
                romeName = rec[2];
                romeIP = rec[1];
                romeCon = rec[4];
                showNe(); // 顯示新訊息提鈕
            }
            break;

        case "2": // 傳檔 (預留)
            break;

        case "9": // 已讀確認
            if (rec[3] == MachineIP)
                MessageBox.Show("訊息來自:" + rec[2] + "(" + rec[1] + ")\r\n" + rec[4], "消息");
            break;
    }
}

發送封包封裝

統一透過 SendPack 將字串轉為位元組陣列並以 UDP 廣播發送:

public void SendPack(string sendStr)
{
    byte[] sendb = Encoding.Default.GetBytes(sendStr);
    try
    {
        listen.Send(sendb, sendb.Length, End);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

使用者介面互動細節

重整線上列表

點擊「重整」鈕會清空列表並重新廣播上線封包:

private void button3_Click(object sender, System.EventArgs e)
{
    box.Items.Clear();
    box.Items.Add("IP                主機名");
    string temp2 = "0&" + MachineIP + "&" + MachineName + "&" + MachineIP;
    SendPack(temp2);
}

發送聊天訊息

需先在列表選取目標使用者,封包格式為 1&MyIP&MyName&TargetIP&Content:

private void button1_Click(object sender, System.EventArgs e)
{
    if (string.IsNullOrEmpty(t_send.Text))
    {
        MessageBox.Show("不能發送空訊息!");
        return;
    }
    if (box.SelectedItem == null || box.SelectedIndex == 0)
    {
        MessageBox.Show("請先選擇一個用戶!");
        return;
    }
    // 解析列表項目 "IP  Name" 取得 IP
    string targetIP = box.SelectedItem.ToString().Split(' ')[0];
    string send = "1&" + MachineIP + "&" + MachineName + "&" + targetIP + "&" + t_send.Text;
    SendPack(send);
}

新訊息提醒與已讀回覆

收到訊息時呼叫 showNe() 將 newmsg 按鈕置頂顯示「有新消息了!」,使用者點擊後將訊息寫入接收區並發送 9 號確認封包:

private void newmsg_Click(object sender, System.EventArgs e)
{
    t_rec.Text = "消息來自 " + romeName + "(" + romeIP + ")" + DateTime.Now.ToShortDateString();
    t_rec.Text += "\r\n> " + romeCon;

    string back = "9&" + MachineIP + "&" + MachineName + "&" + romeIP + "&信息被打開!";
    SendPack(back);
    newmsg.SendToBack();
}

public void showNe()
{
    if (this.WindowState != FormWindowState.Normal)
    {
        this.Visible = true;
        this.WindowState = FormWindowState.Normal;
    }
    newmsg.BringToFront();
}

程式結束時的清理

表單關閉時將 runing 設為 false 並發送一個空封包喚醒 Receive 讓執行緒順利結束:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    runing = false;
    UdpClient mm = new UdpClient();
    IPEndPoint tempIPEnd = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2525);
    string temps = "0&0&0&0&0";
    byte[] sendb = Encoding.Default.GetBytes(temps);
    mm.Send(sendb, sendb.Length, tempIPEnd);
}

潛在改進方向

  • 編碼統一: 目前使用 Encoding.Default,跨語系環境建議改用 UTF-8。
  • 執行緒安全: ListenPort 中直接操作 UI 控制項 (box.Items.Add),應改用 Invoke 或 SynchronizationContext。
  • 傳檔功能: 指令碼 2 已預留,可擴充為分塊傳輸協定。
  • 訊息紀錄: 加入本地 SQLite 或文字檔記錄聊天歷史。
  • NAT 穿透: 若需跨網段通訊,需引入 STUN/TURN 或 UPnP 技術。

結語

此專案展示了利用 C# System.Net.Sockets 命名空間快速構建區域網 P2P 通訊原型的完整流程。雖然功能尚屬基礎,但架構清晰、程式碼精簡,極適合作為學習網路程式設計或二次開發的起點。若您有興趣深入擴充,歡迎 Fork 原始碼並提交 Pull Request!

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