C#实现多线程局域网扫描器的思路与具体代码
思路:
- 获取局域网内所有 IP 地址
- 遍历所有 IP 地址,使用 Ping 命令测试主机是否在线
- 如果主机在线,则扫描主机上的所有端口,确定哪些端口是开放的
- 输出扫描结果
在上述过程中,第 2 步和第 3 步都可以使用多线程来加速。具体来说,可以将 IP 地址分成若干段,每个线程负责扫描一段 IP 地址。对于每个 IP 地址,也可以启动一个线程来扫描其端口。
具体代码:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
namespace LANScanner
{
class Program
{
// 扫描参数
static int numThreads = 100;
static int timeout = 100;
static void Main(string[] args)
{
// 获取本地 IP 地址和子网掩码
IPAddress[] addresses = Dns.GetHostAddresses(Dns.GetHostName());
IPAddress subnetMask = null;
foreach (NetworkInterface adapter in NetworkInterface.GetAllNetworkInterfaces())
{
IPInterfaceProperties properties = adapter.GetIPProperties();
foreach (UnicastIPAddressInformation addressInfo in properties.UnicastAddresses)
{
if (addressInfo.Address.AddressFamily == AddressFamily.InterNetwork)
{
subnetMask = addressInfo.IPv4Mask;
break;
}
}
if (subnetMask != null)
{
break;
}
}
// 计算子网地址和广播地址
IPAddress subnetAddress = GetSubnetAddress(addresses[0], subnetMask);
IPAddress broadcastAddress = GetBroadcastAddress(addresses[0], subnetMask);
// 创建线程池并开始扫描
List<WaitHandle> handles = new List<WaitHandle>();
ThreadPool.SetMinThreads(numThreads, numThreads);
for (int i = 1; i <= 254; i++)
{
IPAddress ip = GetIPAddress(subnetAddress, i);
handles.Add(new ManualResetEvent(false));
ThreadPool.QueueUserWorkItem(PingHost, new object[] { ip, handles.Last() });
}
WaitHandle.WaitAll(handles.ToArray());
Console.WriteLine("Scanning complete.");
Console.ReadLine();
}
static void PingHost(object args)
{
// 解析参数
object[] parameters = (object[])args;
IPAddress ip = (IPAddress)parameters[0];
ManualResetEvent handle = (ManualResetEvent)parameters[1];
// 测试主机是否在线
Ping ping = new Ping();
PingReply reply = ping.Send(ip, timeout);
if (reply.Status == IPStatus.Success)
{
Console.WriteLine("Host {0} is online.", ip);
// 扫描主机上的端口
List<WaitHandle> handles = new List<WaitHandle>();
for (int port = 1; port <= 65535; port++)
{
handles.Add(new ManualResetEvent(false));
ThreadPool.QueueUserWorkItem(CheckPort, new object[] { ip, port, handles.Last() });
}
WaitHandle.WaitAll(handles.ToArray());
}
// 通知主线程完成
handle.Set();
}
static void CheckPort(object args)
{
// 解析参数
object[] parameters = (object[])args;
IPAddress ip = (IPAddress)parameters[0];
int port = (int)parameters[1];
ManualResetEvent handle = (ManualResetEvent)parameters[2];
// 尝试连接端口
try
{
using (TcpClient client = new TcpClient())
{
client.Connect(ip, port);
Console.WriteLine("Port {0} is open on host {1}.", port, ip);
}
}
catch (Exception)
{
// 端口未打开
}
// 通知主线程完成
handle.Set();
}
static IPAddress GetSubnetAddress(IPAddress address, IPAddress subnetMask)
{
byte[] addressBytes = address.GetAddressBytes();
byte[] maskBytes = subnetMask.GetAddressBytes();
byte[] subnetBytes = new byte[4];
for (int i = 0; i < 4; i++)
{
subnetBytes[i] = (byte)(addressBytes[i] & maskBytes[i]);
}
return new IPAddress(subnetBytes);
}
static IPAddress GetBroadcastAddress(IPAddress address, IPAddress subnetMask)
{
byte[] addressBytes = address.GetAddressBytes();
byte[] maskBytes = subnetMask.GetAddressBytes();
byte[] broadcastBytes = new byte[4];
for (int i = 0; i < 4; i++)
{
broadcastBytes[i] = (byte)(addressBytes[i] | ~maskBytes[i]);
}
return new IPAddress(broadcastBytes);
}
static IPAddress GetIPAddress(IPAddress subnetAddress, int host)
{
byte[] subnetBytes = subnetAddress.GetAddressBytes();
byte[] hostBytes = new byte[4];
hostBytes[3] = (byte)host;
byte[] ipBytes = new byte[4];
for (int i = 0; i < 4; i++)
{
ipBytes[i] = (byte)(subnetBytes[i] | hostBytes[i]);
}
return new IPAddress(ipBytes);
}
}
}
上述代码实现了一个简单的多线程局域网扫描器,支持 Ping 主机和扫描主机端口,并输出扫描结果。需要注意的是,在实际应用中,可能需要对输入进行严格的验证和过滤,以确保系统安全。
同时,在使用多线程和异步 I/O 操作时,也需要注意程序的正确性和健壮性。如果不正确地使用这些技术,可能会导致程序出现各种问题,例如竞态条件和死锁。因此,在使用这些技术时,请务必小心并仔细测试程序。
要解决错误"WaitHandles的数量必须小于或等于64",需要将句柄列表拆分为更小的块,并使用WaitAll分别等待每个块。例如,你可以将句柄列表分成4个块,每个块25个句柄,然后分别等待每个块:
List<WaitHandle>[] handleChunks = new List<WaitHandle>[4];
for (int i = 0; i < handleChunks.Length; i++)
{
handleChunks[i] = new List<WaitHandle>();
}
for (int i = 1; i <= 254; i++)
{
// ...
int chunkIndex = (i - 1) % handleChunks.Length;
handleChunks[chunkIndex].Add(new ManualResetEvent(false));
ThreadPool.QueueUserWorkItem(PingHost, new object[] { ip, handleChunks[chunkIndex].Last() });
}
for (int i = 0; i < handleChunks.Length; i++)
{
WaitHandle.WaitAll(handleChunks[i].ToArray());
}