一、创建工程
Visual Studio 2015中新建一个工程,选择windows窗体应用程序。
二、图形界面设计
老师提供了一个完整的多线程版本的端口扫描器,于是我就自己写了一个单线程版本的端口扫描器,图形界面模仿了老师的给出的设计
老师的版本在progerssbox下方还用了三个隐藏的lable来显示端口号和当前扫描的端口,我这里没有放。
三、单线程端口扫描
代码如下
using System;
using System.Windows.Forms;
using System.Net.Sockets;
namespace singlesanapp4
{
public partial class Form1 : Form
{ //自定义变量
private int port;//记录当前扫描的端口号
private string Address;//记录扫描的系统地址
private bool[] done = new bool[65536];//记录端口的开放状态
private int start;//记录扫描的起始端口
private int end;//记录扫描的结束端口
private bool OK;
public Form1()
{
InitializeComponent();
listBox1.Items.Add("欢迎使用端口扫描器 v1.0(单线程)");
this.Text = "端口扫描器 v1.0";
}
private void button1_Click(object sender, EventArgs e)
{
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;
}
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
{
}
}
}
}
运行效果如下
可以看到是很慢的,我特意只扫描了五个端口,花了七秒左右的时间。
四、多线程说明和方法
线程(Thread)是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。
多线程编程的第一步,创建线程。创建线程其实是增加了一个控制流程,使得同一进程中存在多个控制流程并发或者并行执行。有时候我们需要多个线程相互协作来执行,这时需要线程间同步。详见多线程编程
创建多线程的步骤:
1、编写线程所要执行的方法
2、实例化Thread类,并传入一个指向线程所要执行方法的委托。(这时线程已经产生,但还没有运行)
3、调用Thread实例的Start方法,标记该线程可以被CPU执行了,但具体执行时间由CPU决定,详见c#多线程
//创建无参的线程
Thread thread1 = new Thread(new ThreadStart(Thread1));
//调用Start方法执行线程
thread1.Start();
/// 创建无参的方法
/// </summary>
static void Thread1()
{
Console.WriteLine("这是无参的方法");
}
五、多线程端口扫描
老师给的代码是用老版本的VS写的,自动生成的代码 放在了program.cs里,使代码显得冗长,我这里就放各部分核心代码展示一下。
这里有三个隐藏的lable,用来在运行后显示数字
1、
窗口初始化
private void lbResult_SelectedIndexChanged(object sender, EventArgs e)
{
throw new NotImplementedException();
}
[STAThread]
static void Main()
{
// Control.CheckForIllegalCrossThreadCalls = false;
Application.Run(new Form1());
}
private void txtStart_TextChanged(object sender, System.EventArgs e)
{
//获取输入的起始端口值
lblStart.Text = txtStart.Text;
}
private void txtEnd_TextChanged(object sender, System.EventArgs e)
{
//获取输入的接受端口值
lblStop.Text = txtEnd.Text;
}
2、
button 按钮 click 事件(创建线程,并创建ThreadStart委托对象)
private void btnScan_Click(object sender, System.EventArgs e)
{
//创建线程,并创建ThreadStart委托对象
Thread process = new Thread(new ThreadStart(PortScan));
process.Start();
//显示端口扫描的范围
progressBar1.Minimum = Int32.Parse(txtStart.Text);
progressBar1.Maximum = Int32.Parse(txtEnd.Text);
//显示框初始化
lbResult.Items.Clear();
lbResult.Items.Add("端口扫描器 v1.0.");
lbResult.Items.Add("");
}
3、
线程委托对象的无参数方法Portscan
private void PortScan()
{
start = Int32.Parse(txtStart.Text);
end = Int32.Parse(txtEnd.Text);
//检查输入范围合法性
if ((start >= 0 && start <= 65536) && (end >= 0 && end <= 65536) && (start <= end))
{
lbResult.Items.Add("开始扫描... (可能需要请您等待几分钟)");
Addr = txtAddr.Text;
for (int i = start; i <= end; i++)
{
port = i;
//使用该端口的扫描线程
scanThread = new Thread(new ThreadStart(Scan));
scanThread.Start();
//使线程睡眠
System.Threading.Thread.Sleep(100);
progressBar1.Value = i;
lblNow.Text = i.ToString();
}
//未完成时情况
while (!OK)
{
OK = true;
for (int i = start; i <= end; i++)
{
if (!done[i])
{
OK = false;
break;
}
}
System.Threading.Thread.Sleep(1000);
}
lbResult.Items.Add("扫描结束!");
}
else
{
MessageBox.Show("输入错误,端口范围为[0-65536]");
}
}
4、
线程委托对象的无参方法Scan
private void Scan()
{
int portnow = port;
//创建线程变量
Thread Threadnow = scanThread;
done[portnow] = true;
//创建TcpClient对象,TcpClient用于为TCP网络服务提供客户端连接
TcpClient objTCP = null;
//扫描端口,成功则写入信息
try
{
//用TcpClient对象扫描端口
objTCP = new TcpClient(Addr, portnow);
lbResult.Items.Add("端口 " + portnow.ToString() + " 开放!");
}
catch
{
}
}
就这样自运行会中断,原因是未允许跨线程调用,Vs直接提供了微软官方的问题说明
那么解决办法之一就是不再捕获对错误线程的调用,不安全但是比较简单(主要是我们这个程序简单,没那么容易出安全性问题)
向public Form1()中添加一句
CheckForIllegalCrossThreadCalls = false;
官方推荐的解决办法是Invoke方法:将Invoke方法与委托一起使用,如果线程ID不同,它将使用主线程的委托来调用Control.Invoke方法,从而对控件进行实际调用。
this.Invoke(new EventHandler(delegate
{
}));
运行情况如下
可以看到运行起来快了很多。
六、总结
多线程是一个程序员的基本功吧,还要多练习运用。程序员应该追求用各种方法优化代码。