一、前期准备
- 创建工程
打开Virtual Studio
选择创建项目的类型
填写项目一些信息
- 设置界面
二、单线程实现端口扫描
- 代码
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
{
}
}
}
}
- 分析
首先整个过程是依靠单条线程,先对输入开始端口到结束端口进行连接,连接成功,表示这个端口是开放的,并将其记录下来。然后就是将开放的端口在ListBox中显示出来。 - 运行结果
三、多线程实现端口扫描
- 代码
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
{
}
}
}
}
- 分析
整个过程使用多个线程来实现扫描端口,次线程的进行并不会影响主线程的进行,线程之间不会相互影响。 - 运行效果
本机的端口开放情况
树莓派端口开放情况
- 扩展
运行过程可能会遇到不能从不是创建该控件的线程调用它的问题。
解决方式:
①采用忽略对跨线程调用的检测
这种方法存在着一些安全性的问题。
②采用委托的方式(invoke)public Form1() { InitializeComponent(); CheckForIllegalCrossThreadCalls = false; }
该方式是通过创建一个委托来实现对控件的参数修改。
例如://button还可以改成其他控件名称,如果比较多的控件时,可以使用this button.Invoke(new EventHandler(delegate { //其他线程创建的控件的参数的一些修改语句 }));
四、两种方式的对比
从两种的运行结果来看,单线程会出现卡顿的情况,而多线程的方式不会产生卡顿。单线程是一个端口一次进行扫描,多线程则是以线程的方式每个线程进行一个端口的扫描,很明显可以看出多线程扫描要比单线程扫描用的时间更少。
五、小结
通过两种方式来实现端口扫描,可以清楚了解到单线程和多线程之间的区别。在一些情况下,采用多线程会使程序更加优化,实现功能效率更高。总的来说,采用多线程的方式具有较大的优势。该程序用来查看系统开启哪些端口服务。