用C#实现端口扫描器小程序

一、UI 设计

  1. VS2017 新建一个 Windows 窗体应用(.NET Framework) 的项目

  2. 点击进入 From.cs[设计],可将界面设计如下:

二、只用单一进程实现

  1. 核心代码

            //用单一进程测试端口扫描
            private void main_Thread()
            {
                listBox1.Items.Add("开始扫描……(该过程可能会等待一定时间!)");
                for (int i = startPort; i <= endPort; i++)
                {
                    progressBar1.Value = i;
                    label5.Text = i.ToString();
                    try
                    {
                        TcpClient tcp = new TcpClient(IP, i);
                        listBox1.Items.Add("端口 " + i.ToString() + " 开放!");
                    }
                    catch
                    {
                    }
                }
            }
    
  2. 效果如下

    由图可见,程序执行十分的卡顿,窗体界面已经不能拖动了。此时,应该用多线程的方式解决。

三、多线程的说明

  1. 线程: 是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。

  2. 多线程: 是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

  3. 多线程的好处: 可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。

四、用多线程方式实现

  1. C# 实现多线程有多种方法,这里主要用 Thread类 实现

    a.Thread类 创建线程

    ThreadStart childref = new ThreadStart(CallToChildThread);
    Thread childThread = new Thread(childref);
    childThread.Start();
    

    ThreadStart 委托定义了一个返回类型为void的无参数方法(即,CallToChildThread为一个void类型的无参函数)

    Thread类 的构造函数重载为接受 ThreadStartParameterizedThreadStart 类型的委托参数

    Start() 方法启动线程

    Thread类 的更多属性和方法请参考:C# 多线程

  2. 实现流程大致如下

  3. 核心代码

    a. 添加 button 按钮 click 事件

    		private void button_Click(object sender, EventArgs e)
            {
                //创建线程,并创建 ThreadStart 委托对象
                ThreadStart childScan = new ThreadStart(port_Scan);
                Thread childThread = new Thread(childScan);
                childThread.Start();
                //显示端口扫描范围
                progressBar1.Minimum = startPort;
                progressBar1.Maximum = endPort;
                //main_Thread();
                //显示初始化端口信息
                listBox1.Items.Clear();
                listBox1.Items.Add("欢迎使用端口扫描器 v1.0!!!");
            }
    

    b. 线程 childThread 传入的 ThreadStart 委托对象的返回类型为void的无参数方法 port_Scan()

    private void port_Scan()
            {
                //检查端口合法性
                if((startPort>=0 && startPort<=65535) && (endPort>=0 && endPort<=65535) && (startPort <= endPort))
                {
                    if(IP!="" && judge_IP())
                    {
                        listBox1.Items.Clear();
                        listBox1.Items.Add("开始扫描……(该过程可能会等待一定时间!)");
                        for (int i=startPort; i <= endPort; i++)
                        {
                            port = i;
                            //创建该端口的扫描线程
                            ThreadStart childScan = new ThreadStart(Scan);
                            Thread scanThread = new Thread(childScan);
                            scanThread.Start();
                            //使线程暂停100ms
                            System.Threading.Thread.Sleep(100);
                            progressBar1.Value = i;
                            label5.Text = i.ToString();
                        }
                        bool flag = true;
                        while (flag)
                        {
                            int i;
                            for(i = startPort; i <= endPort; i++)
                            {
                                if (!done[i])
                                {
                                    break;
                                }
                            }
                            if (i == endPort + 1)
                            {
                                flag = false;
                            }
                            //线程暂停2000ms
                            System.Threading.Thread.Sleep(2000);
                        }
                        listBox1.Items.Add("扫描结束!!!");
                    }
                    else
                    {
                        MessageBox.Show("IP地址不合法!", "Error", MessageBoxButtons.OKCancel, MessageBoxIcon.Error);
                    }
                }
                else
                {
                    //错误消息反馈
                    MessageBox.Show("端口不合法!", "Error", MessageBoxButtons.OKCancel,MessageBoxIcon.Error);
                }
            }
    

    c. 线程 scanThread传入的 ThreadStart 委托对象的返回类型为void的无参数方法 Scan()

    private void Scan()
            {
                int portnow = port;
                //记录该端口已访问
                done[portnow] = true;
                try
                {
                    TcpClient tcp = new TcpClient(IP, portnow);
                    listBox1.Items.Add("端口 " + portnow.ToString() + " 开放!");
                }
                catch
                {
                }
            }
    
  4. 异常错误处理

    错误原因是因为子线程修改了主线程中的 UI控件的参数

    a. 解决方案一:

    在窗体的 构造函数Form1() 中添加如下代码:

    System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
    

    即:禁止捕获对错误线程的调用,但该方法存在一定的安全性

    b. 采用委托的方式(微软官方推荐)

    this.Invoke((EventHandler)delegate
    {
    	listBox1.Items.Clear();
    	button1.Enabled = false;
    	listBox1.Items.Add("开始扫描……(该过程可能会等待一定时间!)");
    });
    

    即:子线程应该使用一个委托让UI线程来执行修改UI控件参数的操作

    Invoke方法的说明和用法具体可参考:C#中Invoke的用法()

  5. 最终实现效果

    与单一的进程相比,程序的流畅性有了显著的提高

五、参考🔗

六、完整源码

链接:https://pan.baidu.com/s/1jr5ro74MwI5gLpja8y4vjQ
提取码:n05v

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值