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#当中是采用委托来解决这一问题。

推荐阅读

    设置线程名称|tomcat线程名称设置

    设置线程名称|tomcat线程名称设置,,1. tomcat线程名称设置一.tomcat的优化1.tomcat的自身调优采用动静分离调优Tomcat线程池调优Tomcat的

    多线程cpu电脑|多线程的CPU

    多线程cpu电脑|多线程的CPU,,1. 多线程的CPU四核心四线程,表示这个电脑的CPU核心是4个核心、4个线程的。电脑CPU的核心数量和线程数量越多,

    简单的线程池(三)

    简单的线程池(三),吞吐量,线程,◆ 概要本文中,作者针对 《简单的线程池(一)》 和 《简单的线程池(二)》 介绍的两个线程池分别进行了并发测试。基

    foreach的用法c语言和c#

    foreach的用法c语言和c#,数组,遍历,本文目录foreach的用法c语言和c#详细讲解foreach循环的用法C#中的foreach 怎么用啊foreach用法C#中的f

    Common Lisp支持多线程吗

    Common Lisp支持多线程吗,语言,都是,本文目录Common Lisp支持多线程吗能推荐下比较经典的common lisp 代码么相比Scheme 与 Common Lisp,Cl