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

.NET 網路程式設計實戰系列(三):打造最簡易的 TCP 伺服器

     編輯:關於.NET

伺服器的核心職責

伺服器的任務很單純:持續監聽客戶端連線請求,並對收到的訊息做出回應。雖然我們要打造的是「世界上最簡陋的伺服器」,但仍有幾個關鍵細節必須注意,否則輕易導致介面凍結或資源洩漏。

四大關鍵實作細節

1. 獨立執行緒避免 UI 假死

監聽迴圈屬於封鎖式作業,若直接在 UI 執行緒執行,整個視窗將失去回應。解決方案是啟動一條背景執行緒專責監聽:

Thread td = new Thread(Listen);
td.IsBackground = true; // 關鍵:設為背景執行緒
td.Start();

2. 設定 IsBackground = true 確保程式正常退出

若不將執行緒標記為背景執行緒,當使用者關閉視窗時,主程式雖結束,但前景執行緒仍在運作,導致進程殘留。設定 td.IsBackground = true 可讓執行緒隨應用程式一同終止。

3. 跨執行緒存取 UI 必須用 Invoke

在監聽執行緒中直接操作 UI 控制項(如 RichTextBox)會拋出跨執行緒例外。必須透過委派與 Invoke 回到 UI 執行緒更新介面:

public delegate void ChangeRichTextBoxHandler(RichTextBox rtb, string str);
public void ChangeRichTextBox(RichTextBox rtb, string str)
{
    rtb.AppendText(str);
}

// 在監聽執行緒中呼叫:
Invoke(new ChangeRichTextBoxHandler(ChangeRichTextBox), this.richTextBox1, "有客戶端連接了我");

4. Thread.Sleep(1) 降低 CPU 佔用

無限迴圈若無休眠會佔滿 CPU 核心。加入 Thread.Sleep(1) 可大幅降低 CPU 使用率,且不影響連線接收(作業系統網路堆疊會緩衝封包)。

加入 Sleep 後的 CPU 使用率 未加入 Sleep 的 CPU 使用率

完整伺服器端程式碼

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace Server
{
    public partial class Form1 : Form
    {
        public Form1() { InitializeComponent(); }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread td = new Thread(Listen);
            td.IsBackground = true;
            td.Start();
            this.button1.Enabled = false;
            MessageBox.Show("伺服器成功開啟");
        }

        public delegate void ChangeRichTextBoxHandler(RichTextBox rtb, string str);
        public void ChangeRichTextBox(RichTextBox rtb, string str)
        {
            rtb.AppendText(str);
        }

        private void Listen()
        {
            IPAddress ip = Dns.GetHostAddresses(Dns.GetHostName())[0];
            int port = Int32.Parse(this.textBox1.Text.Trim());
            TcpListener tl = new TcpListener(ip, port);
            tl.Start();

            while (true)
            {
                if (tl.Pending())
                {
                    Socket newSocket = tl.AcceptSocket();
                    byte[] buff = new byte[9];
                    int length = newSocket.Receive(buff);
                    string command = Encoding.Default.GetString(buff);
                    if (command == "conServer")
                    {
                        Invoke(new ChangeRichTextBoxHandler(ChangeRichTextBox), 
                               this.richTextBox1, "有客戶端連接了我");
                    }
                }
                Thread.Sleep(1); // 關鍵:降低 CPU 佔用
            }
        }
    }
}

客戶端測試程式碼

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Client
{
    public partial class Form1 : Form
    {
        public Form1() { InitializeComponent(); }

        private void button1_Click(object sender, EventArgs e)
        {
            TcpClient tcpClient = new TcpClient();
            tcpClient.Connect(Dns.GetHostAddresses(Dns.GetHostName())[0], 
                              Convert.ToInt32(this.textBox1.Text.Trim()));
            NetworkStream nwStream = tcpClient.GetStream();
            string cmd = "conServer";
            Byte[] bytes = Encoding.Default.GetBytes(cmd.ToCharArray());
            nwStream.Write(bytes, 0, bytes.Length);
        }
    }
}

原始碼下載

完整專案原始碼:世界上最簡陋的服務器.rar

文章出處:https://www.cnblogs.com/zhanglei644213943

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