利用单线程和多线程实现端口扫描器

一、前期准备

  1. 创建工程
    打开Virtual Studio
    在这里插入图片描述
    选择创建项目的类型
    在这里插入图片描述
    填写项目一些信息
    在这里插入图片描述
  2. 设置界面
    在这里插入图片描述

二、单线程实现端口扫描

  1. 代码
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace PortScan
{
    
    
    public partial class Form1 : Form
    {
    
    
        public Form1()
        {
    
    
            InitializeComponent();
        }
        //自定义变量
        private int port;//记录当前扫描的端口号
        private string Address;//记录扫描的系统地址
        private bool[] done = new bool[65536];//记录端口的开放状态
        private int start;//记录扫描的起始端口
        private int end;//记录扫描的结束端口
        private bool OK;

        private void button1_Click(object sender, EventArgs e)
        {
    
    
            label4.Text = textBox2.Text;
            label6.Text = textBox3.Text;
            progressBar1.Minimum = Int32.Parse(textBox2.Text);
            progressBar1.Maximum = Int32.Parse(textBox3.Text);
            listBox1.Items.Clear();
            listBox1.Items.Add("端口扫描器v1.0.");
            listBox1.Items.Add("");
            PortScan();

        }
        private void  PortScan()
        {
    
    
            start = Int32.Parse(textBox2.Text);
            end = Int32.Parse(textBox3.Text);
            //判断输入端口是否合法
            if((start>=0&&start<=65536)&&(end>=0&&end<=65536)&&(start<=end))
            {
    
    
                listBox1.Items.Add("开始扫描:这个过程可能需要等待几分钟!");
                Address = textBox1.Text;
                for(int i = start; i <= end; i++)
                {
    
    
                    port = i;
                    Scan();
                    progressBar1.Value = i;
                    label5.Text = i.ToString();
                }
                while (!OK)
                {
    
    
                    OK = true;
                    for(int i = start; i <= end; i++)
                    {
    
    
                        if (!done[i])
                        {
    
    
                            OK = false;
                            break;
                        }
                    }
                }
                listBox1.Items.Add("扫描结束!");
            }
            else
            {
    
    
                MessageBox.Show("输入错误,端口范围为[0,65536]");
            }
        }
        //连接端口
        private void Scan()
        {
    
    
            int portnow = port;
            done[portnow] = true;
            TcpClient objTCP = null;
            try
            {
    
    
                objTCP = new TcpClient(Address, portnow);
                listBox1.Items.Add("端口"+portnow.ToString()+"开放");
            }
            catch
            {
    
    

            }

        }
    }
}

  1. 分析
    首先整个过程是依靠单条线程,先对输入开始端口到结束端口进行连接,连接成功,表示这个端口是开放的,并将其记录下来。然后就是将开放的端口在ListBox中显示出来。
  2. 运行结果
    在这里插入图片描述

三、多线程实现端口扫描

  1. 代码
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace PortScan
{
    
    
    public partial class Form1 : Form
    {
    
    
        public Form1()
        {
    
    
            InitializeComponent();
        }
        //自定义变量
        private int port;//记录当前扫描的端口号
        private string Address;//记录扫描的系统地址
        private bool[] done = new bool[65536];//记录端口的开放状态
        private int start;//记录扫描的起始端口
        private int end;//记录扫描的结束端口
        private bool OK;
        private Thread scanThread;  
        //将输入的起始端口放到进度条的开始位置
        private void label4_TextChanged(object sender, EventArgs e)
        {
    
    
            label4.Text = textBox2.Text;
        }
        //将输入的结束地址放到进度条的结束位置
        private void label6_TextChanged(object sender, EventArgs e)
        {
    
    
            label6.Text = textBox3.Text;
        }
        private void button1_Click(object sender, EventArgs e)
        {
    
    
            label4_TextChanged(sender, e);
            label6_TextChanged(sender, e);
            //创建线程,并创建ThreadStart委托对象
            Thread procss = new Thread(new ThreadStart(PortScan));
            procss.Start();
            //显示端口扫描范围
            progressBar1.Minimum = Int32.Parse(textBox2.Text);
            progressBar1.Maximum = Int32.Parse(textBox3.Text);
            //显示框的初始化
            listBox1.Items.Clear();
            listBox1.Items.Add("端口扫描器v1.0.");
            listBox1.Items.Add("");

        }
        private void PortScan()
        {
    
    
            start = Int32.Parse(textBox2.Text);
            end = Int32.Parse(textBox3.Text);
            //检查端口的合法性
            if ((start >= 0 && start <= 65536) && (end >= 0 && end <= 65536) && (start <= end))
            {
    
    
                listBox1.Items.Add("开始扫描:这个过程可能需要等待几分钟!");
                Address = textBox1.Text;
                for (int i = start; i <= end; i++)
                {
    
    
                    port = i;
                    //对该端口进行扫描的线程
                    scanThread = new Thread(Scan);
                    scanThread.Start();
                    //使线程睡眠
                    System.Threading.Thread.Sleep(100);
                    progressBar1.Value = i;
                    label5.Text = i.ToString();
                }
                //未完成时情况
                while (!OK)
                {
    
    
                    OK = true;
                    for (int i = start; i <= end; i++)
                    {
    
    
                        if (!done[i])
                        {
    
    
                            OK = false;
                            break;
                        }
                    }
                }
                listBox1.Items.Add("扫描结束!");
                System.Threading.Thread.Sleep(1000);
            }
            else
            {
    
    
                MessageBox.Show("输入错误,端口范围为[0,65536]");
            }
        }
        private void Scan()
        {
    
    
            int portnow = port;
            //创建线程变量
            Thread Threadnow = scanThread;
            done[portnow] = true;
            //创建TcpClient对象,TcpClient用于TCP网络服务提供客户端连接
            TcpClient objTCP = null;
            //扫描端口,成功就写入信息
            try
            {
    
    
                objTCP = new TcpClient(Address, portnow);
                listBox1.Items.Add("端口" + portnow.ToString() + "开放!");
            }
            catch
            {
    
    

            }
        }
    }
}

  1. 分析
    整个过程使用多个线程来实现扫描端口,次线程的进行并不会影响主线程的进行,线程之间不会相互影响。
  2. 运行效果
    本机的端口开放情况
    在这里插入图片描述
    树莓派端口开放情况
    在这里插入图片描述
  3. 扩展
    运行过程可能会遇到不能从不是创建该控件的线程调用它的问题。
    解决方式:
    ①采用忽略对跨线程调用的检测
    这种方法存在着一些安全性的问题。
    public Form1()
    {
          
          
        InitializeComponent();
        CheckForIllegalCrossThreadCalls = false;
    }
    
    ②采用委托的方式(invoke)
    该方式是通过创建一个委托来实现对控件的参数修改。
    例如:
    //button还可以改成其他控件名称,如果比较多的控件时,可以使用this
    button.Invoke(new EventHandler(delegate
    	{
          
          
    	//其他线程创建的控件的参数的一些修改语句
    	}));
    

四、两种方式的对比

从两种的运行结果来看,单线程会出现卡顿的情况,而多线程的方式不会产生卡顿。单线程是一个端口一次进行扫描,多线程则是以线程的方式每个线程进行一个端口的扫描,很明显可以看出多线程扫描要比单线程扫描用的时间更少。

五、小结

通过两种方式来实现端口扫描,可以清楚了解到单线程和多线程之间的区别。在一些情况下,采用多线程会使程序更加优化,实现功能效率更高。总的来说,采用多线程的方式具有较大的优势。该程序用来查看系统开启哪些端口服务。

六、参考资料

  1. 线程间操作无效: 从不是创建控件的线程访问它

猜你喜欢

转载自blog.csdn.net/qq_43279579/article/details/109686607