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#当中是采用委托来解决这一问题。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。