首頁 > 軟體

C#單執行緒和多執行緒的埠掃描器應用比較詳解

2022-07-27 22:04:33

本文章使用C#程式設計,製作一個埠掃描器,能夠掃描本機有哪些埠開放了,並顯示出來,分別使用單執行緒和多執行緒進行了比較。

編譯軟體:Visual Studio 2019
編譯環境:Windows 10
使用語言:C#

一、準備工作

第一步:新建工程

建立新專案。

選擇 Windows 表單應用。

輸入專案名稱(Port_Scanning),選擇程式碼儲存路徑,然後點選建立。

第二步:控制元件擺放

使用控制元件按下圖擺放。

table × 4個
textbox × 4個
progressBar × 1 個
button × 1個
注:圖中紅色的文字為控制元件的ID

修改屬性:點選一下 textbox4 控制元件,將 ReadOnly 屬性設定為 True ,這樣這個文字方塊就唯讀了而不能修改,用於顯示結果的。

其它的字型、大小等屬性可以在 Font 處編輯。

二、埠掃描器(單執行緒)

第一步:編寫程式碼

  • 擺放完畢後,在視窗設計介面內,雙擊 button 按鈕,可以轉到程式碼編輯區。
  • 以下是我的程式碼,也有部分註釋。
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.Sockets;
using System.Threading;

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

        //主機地址
        private string hostAddress;
        //起始埠
        private int start;
        //終止埠
        private int end;
        //埠號
        private int port;
        //定義執行緒物件
        private Thread scanThread;
        

        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                //初始化
                textBox4.Clear();
                label5.Text = "0%";
                //獲取ip地址和始末埠號
                hostAddress = textBox1.Text;
                start = Int32.Parse(textBox2.Text);
                end = Int32.Parse(textBox3.Text);
                if (decideAddress())
                {
                    //讓輸入的textbox唯讀,無法改變
                    textBox1.ReadOnly = true;
                    textBox2.ReadOnly = true;
                    textBox3.ReadOnly = true;
                    //設定進度條的範圍
                    progressBar1.Minimum = start;
                    progressBar1.Maximum = end;
                    //顯示框顯示
                    textBox4.AppendText("埠掃描器 v1.0.0" + Environment.NewLine + Environment.NewLine);
                    //呼叫埠掃描函數
                    PortScan();
                }
                else
                {
                    //若埠號不合理,彈窗報錯
                    MessageBox.Show("輸入錯誤,埠範圍為[0-65536]!");
                }
            }
            catch
            {
                //若輸入的埠號為非整型,則彈窗報錯
                MessageBox.Show("輸入錯誤,埠範圍為[0-65536]!");
            }
        }

        private bool decideAddress()
        {
            //判斷埠號是否合理
            if ((start >= 0 && start <= 65536) && (end >= 0 && end <= 65536) && (start <= end))
                return true;
            else
                return false;
        }

        private void PortScan()
        {
            double x;
            string xian;
            //顯示掃描狀態
            textBox4.AppendText("開始掃描...(可能需要請您等待幾分鐘)" + Environment.NewLine + Environment.NewLine);
            //迴圈丟擲執行緒掃描埠
            for (int i = start; i <= end; i++)
            {
                x = (double)(i - start + 1) / (end - start + 1);
                xian = x.ToString("0%");
                port = i;
                //呼叫埠i的掃描操作
                Scan();
                //進度條值改變
                label5.Text = xian;
                label5.Refresh();
                progressBar1.Value = i;
            }
            textBox4.AppendText(Environment.NewLine + "掃描結束!" + Environment.NewLine);
            //輸入框textbox唯讀屬性取消
            textBox1.ReadOnly = false;
            textBox2.ReadOnly = false;
            textBox3.ReadOnly = false;
        }

        private void Scan()
        {
            int portnow = port;
            //建立TcpClient物件,TcpClient用於為TCP網路服務提供使用者端連線
            TcpClient objTCP = null;
            try
            {
                //用於TcpClient物件掃描埠
                objTCP = new TcpClient(hostAddress, portnow);
                //掃描到則顯示到顯示框
                textBox4.AppendText("埠 " + port + " 開放!" + Environment.NewLine);
            }
            catch
            {
                //未掃描到,則會丟擲錯誤
            }
        }
    }
}

下圖為單執行緒程式的執行過程,整個流程都是依次進行的。

編譯執行以下,看看結果。

第二步:執行結果

這裡說明一下:127.0.0.1這個 IP 地址代指自己的主機,不能用自己主機真實的 IP 地址。

可以看到掃描的速度是比較慢的。

三、埠掃描器(多執行緒)

第一步:編寫程式碼

將單執行緒的程式碼稍微修改一下,加入多執行緒。

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.Sockets;
using System.Threading;

namespace Port_Scanning
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            //不進行跨執行緒檢查
            CheckForIllegalCrossThreadCalls = false;
        }

        //主機地址
        private string hostAddress;
        //起始埠
        private int start;
        //終止埠
        private int end;
        //埠號
        private int port;
        //定義執行緒物件
        private Thread scanThread;
        //定義埠狀態資料(開放則為true,否則為false)
        private bool[] done = new bool[65526];
        private bool OK;

        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                //初始化
                textBox4.Clear();
                label5.Text = "0%";
                //獲取ip地址和始末埠號
                hostAddress = textBox1.Text;
                start = Int32.Parse(textBox2.Text);
                end = Int32.Parse(textBox3.Text);
                if (decideAddress())
                {
                    textBox1.ReadOnly = true;
                    textBox2.ReadOnly = true;
                    textBox3.ReadOnly = true;
                    //建立執行緒,並建立ThreadStart委託物件
                    Thread process = new Thread(new ThreadStart(PortScan));
                    process.Start();
                    //設定進度條的範圍
                    progressBar1.Minimum = start;
                    progressBar1.Maximum = end;
                    //顯示框顯示
                    textBox4.AppendText("埠掃描器 v1.0.0" + Environment.NewLine + Environment.NewLine);
                }
                else
                {
                    //若埠號不合理,彈窗報錯
                    MessageBox.Show("輸入錯誤,埠範圍為[0-65536]!");
                }
            }
            catch
            {
                //若輸入的埠號為非整型,則彈窗報錯
                MessageBox.Show("輸入錯誤,埠範圍為[0-65536]!");
            }
        }

        private bool decideAddress()
        {
            //判斷埠號是否合理
            if ((start >= 0 && start <= 65536) && (end >= 0 && end <= 65536) && (start <= end))
                return true;
            else
                return false;
        }

        private void PortScan()
        {
            double x;
            string xian;
            //顯示掃描狀態
            textBox4.AppendText("開始掃描...(可能需要請您等待幾分鐘)" + Environment.NewLine + Environment.NewLine);
            //迴圈丟擲執行緒掃描埠
            for (int i = start; i <= end; i++)
            {
                x = (double)(i - start + 1) / (end - start + 1);
                xian = x.ToString("0%");
                port = i;
                //使用該埠的掃描執行緒
                scanThread = new Thread(new ThreadStart(Scan));
                scanThread.Start();
                //使執行緒睡眠
                System.Threading.Thread.Sleep(100);
                //進度條值改變
                label5.Text = xian;
                progressBar1.Value = i;
            }
            while (!OK)
            {
                OK = true;
                for (int i = start; i <= end; i++)
                {
                    if (!done[i])
                    {
                        OK = false;
                        break;
                    }
                }
                System.Threading.Thread.Sleep(1000);
            }
            textBox4.AppendText(Environment.NewLine + "掃描結束!" + Environment.NewLine);
            textBox1.ReadOnly = false;
            textBox2.ReadOnly = false;
            textBox3.ReadOnly = false;
        }

        private void Scan()
        {
            int portnow = port;
            //建立執行緒變數
            Thread Threadnow = scanThread;
            //掃描埠,成功則寫入資訊
            done[portnow] = true; 
            //建立TcpClient物件,TcpClient用於為TCP網路服務提供使用者端連線
            TcpClient objTCP = null;
            try
            {
                //用於TcpClient物件掃描埠
                objTCP = new TcpClient(hostAddress, portnow);
                //掃描到則顯示到顯示框
                textBox4.AppendText("埠 " + port + " 開放!" + Environment.NewLine);
            }
            catch
            {
                //未掃描到,則會丟擲錯誤
            }
        }
    }
}

這是程式碼的執行流程,可以更加直觀的看到程式如何執行的,利於理解多執行緒的含義。

這裡提一句,程式碼中的建構函式中:CheckForIllegalCrossThreadCalls = false;,這一句是直接跳過跨執行緒檢查,如果程式不當,會造成死迴圈,建議使用委託 delegate ,網上有很多關於委託的講解,我不太熟悉,經過幾次試驗後,程式執行的時候,輸出顯示的結果的先後順序會有點不同,這一點需要改進。

第二步:執行結果

可以看到多執行緒的埠掃描器的速度要比單執行緒的快很多。

四、總結

多執行緒就好比是把單執行緒的總量分成了多條線路同時進行,自然是要快很多,目前絕大多數的應用程式都是採用的多執行緒,掌握多執行緒程式設計是一個實戰程式設計師應會的技能,但跨執行緒控制控制元件,會遇到問題,子執行緒控制主執行緒的控制元件,會容易造成死迴圈,在C#當中是採用委託來解決這一問題。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com