C#單線程和多線程的端口掃描器應用比較詳解
本文章使用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#當中是采用委托來解決這一問題。
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。