一、单线程与多线程的区别
- 单线程:
每个正在运行的程序(即进程),至少包括一个线程,这个线程叫主线程
主线程在程序启动时被创建,用于执行main函数
只有一个主线程的程序,称作单线程程序
主线程负责执行程序的所有代码(UI展现以及刷新,网络请求,本地存储等等)。这些代码只能顺序执行,无法并发执行 - 多线程:
拥有多个线程的程序,称作多线程程序。
允许用户自己开辟新的线程,相对于主线程来讲,这些线程,称为子线程
可以根据需要开辟若干子线程
子线程和主线程都是独立的运行单元,各自的执行互不影响,因此能够并发执行 - 单线程、多线程的区别:
单线程程序:只有一个线程,代码顺序执行,容易出现代码阻塞(页面假死)
多线程程序:有多个线程,线程间独立运行,能有效地避免代码阻塞,并且提高程序的运行性能
二、端口扫描程序设计
1. 创建项目
- 选择windows窗体应用
- 项目配置
- 初始界面
2. 界面设计
3. 单线程代码
namespace ScanPort_Single
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;//设置该属性 为false,将禁止捕获对错误线程的调用
}
//自定义变量
//单线程
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)
{
start = Int32.Parse(textBox2.Text);
end = Int32.Parse(textBox3.Text);
label4.Text = start.ToString();
label6.Text = end.ToString();
label4.Visible = true;
label6.Visible = true;
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()
{
//判断输入端口是否合法
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();
label5.Visible = true;
}
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
{
}
}
}
}
4. 多线程代码
namespace ScanPort_Single
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;//设置该属性 为false,将禁止捕获对错误线程的调用
}
//自定义变量
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;
label4.Visible = true;
}
//将输入的结束地址放到进度条的结束位置
private void label6_TextChanged(object sender, EventArgs e)
{
label6.Text = textBox3.Text;
label6.Visible = true;
}
private void button1_Click(object sender, EventArgs e)
{
label4_TextChanged(sender, e);
label6_TextChanged(sender, e);
//创建线程,并创建ThreadStart委托对象
Thread procss = new Thread(new ThreadStart(ScanPort_Thread));
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("");
ScanPort_Thread();
}
private void ScanPort_Thread()
{
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();
label5.Visible = true;
}
//未完成时情况
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
{
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
}
}
说明:当使用多线程功能时,在子线程里修改UI控件参数时,会报错
异常:System.InvalidOperationException:“线程间操作无效: 从不是创建控件“listBox1”的线程访问它。”
解决办法在Form1重载中写上一行代码
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;//设置该属性 为false,将禁止捕获对错误线程的调用
也可以
listBox1.Invoke(new EventHandler(delegate
{
//其他线程创建的控件的参数的一些修改语句
}));
三、端口扫描程序运行效果
1. 单线程
2. 多线程
整个过程使用多个线程来实现扫描端口,次线程的进行并不会影响主线程的进行,线程之间不会相互影响。
对比单线程,多线程时间明显更短,单线程会出现卡顿的情况,而多线程的方式不会产生卡顿。