作为一名C#开发者,你是否曾经在排查网络问题时手忙脚乱?服务器端口是否开放、网络连接是否正常、防火墙配置是否生效... 这些问题让多少程序员深夜难眠。
今天,通过一个完整的端口扫描器项目,带你掌握C#网络编程的核心技术,从此告别网络问题排查的痛苦!
我们要开发一个功能完整的Windows Forms端口扫描器,它具备以下核心功能:
C#private async Task ScanPortsAsync(string target, int startPort, int endPort,
int threadCount, int timeout, CancellationToken cancellationToken)
{
var semaphore = new SemaphoreSlim(threadCount, threadCount);
var tasks = new List<Task>();
for (int port = startPort; port <= endPort; port++)
{
if (cancellationToken.IsCancellationRequested)
break;
int currentPort = port;
var task = Task.Run(async () =>
{
await semaphore.WaitAsync(cancellationToken);
try
{
await ScanPortAsync(target, currentPort, timeout, cancellationToken);
}
finally
{
semaphore.Release();
}
}, cancellationToken);
tasks.Add(task);
}
await Task.WhenAll(tasks);
}
⚡ 性能优化要点:
SemaphoreSlim
控制并发数,防止创建过多连接finally
块确保资源正确释放C#private async Task ScanPortAsync(string target, int port, int timeout,
CancellationToken cancellationToken)
{
try
{
using (var client = new TcpClient())
{
cancellationToken.ThrowIfCancellationRequested();
var connectTask = client.ConnectAsync(target, port);
var timeoutTask = Task.Delay(timeout, cancellationToken);
var completedTask = await Task.WhenAny(connectTask, timeoutTask);
if (completedTask == connectTask && client.Connected)
{
string service = GetServiceName(port);
var result = new ScanResult
{
Host = target,
Port = port,
IsOpen = true,
Service = service,
ScanTime = DateTime.Now
};
lock (lockObject)
{
scanResults.Add(result);
openPorts++;
}
Invoke(new Action(() =>
{
AddResult($"端口 {port} 开放 - {service}", Color.Green);
}));
}
}
}
catch (OperationCanceledException)
{
throw;
}
catch
{
}
finally
{
lock (lockObject)
{
scannedPorts++;
}
Invoke(new Action(() =>
{
progressBar.Value = scannedPorts;
lblStatus.Text = $"正在扫描... ({scannedPorts}/{totalPorts}) 已发现 {openPorts} 个开放端口";
}));
}
}
🎯 关键技术点:
Task.WhenAny
实现超时控制using
语句确保TCP连接释放lock
保证多线程数据安全Invoke
实现跨线程UI更新C#private string GetServiceName(int port)
{
var commonPorts = new Dictionary<int, string>
{
{21, "FTP"}, // 文件传输协议
{22, "SSH"}, // 安全外壳协议
{23, "Telnet"}, // 远程登录
{25, "SMTP"}, // 邮件发送
{53, "DNS"}, // 域名解析
{80, "HTTP"}, // 网页服务
{110, "POP3"}, // 邮件接收
{135, "RPC"}, // 远程过程调用
{139, "NetBIOS"}, // 网络基本输入输出系统
{143, "IMAP"}, // 邮件接收协议
{443, "HTTPS"}, // 安全网页服务
{445, "SMB"}, // 服务器消息块
{993, "IMAPS"}, // 安全IMAP
{995, "POP3S"}, // 安全POP3
{1433, "MSSQL"}, // SQL Server数据库
{1521, "Oracle"}, // Oracle数据库
{3306, "MySQL"}, // MySQL数据库
{3389, "RDP"}, // 远程桌面
{5432, "PostgreSQL"}, // PostgreSQL数据库
{5900, "VNC"}, // 虚拟网络计算
{6379, "Redis"}, // Redis缓存数据库
{8080, "HTTP-Alt"}, // 备用HTTP端口
{8443, "HTTPS-Alt"} // 备用HTTPS端口
};
return commonPorts.ContainsKey(port) ? commonPorts[port] : "Unknown";
}
C#public partial class Form1 : Form
{
private CancellationTokenSource cancellationTokenSource;
private async void StartScan()
{
try
{
cancellationTokenSource = new CancellationTokenSource();
await ScanPortsAsync(target, startPort, endPort, threadCount,
timeout, cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
lblStatus.Text = "扫描已取消";
AddResult("=== 扫描已取消 ===", Color.Red);
}
}
private void StopScan()
{
cancellationTokenSource?.Cancel(); // 触发取消
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
cancellationTokenSource?.Cancel(); // 窗体关闭时取消所有任务
}
}
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AppPortScanner
{
public partial class Form1 : Form
{
private CancellationTokenSource cancellationTokenSource;
private readonly object lockObject = new object();
private int totalPorts;
private int scannedPorts;
private int openPorts;
private readonly List<ScanResult> scanResults = new List<ScanResult>();
public Form1()
{
InitializeComponent();
InitializeForm();
}
private void InitializeForm()
{
this.Text = "端口扫描工具 v1.0";
UpdateUI(false);
lblStatus.Text = "就绪";
}
private void btnScan_Click(object sender, EventArgs e)
{
if (!ValidateInput())
return;
StartScan();
}
private void btnStop_Click(object sender, EventArgs e)
{
StopScan();
}
private void btnClear_Click(object sender, EventArgs e)
{
lstResults.Items.Clear();
scanResults.Clear();
progressBar.Value = 0;
lblStatus.Text = "就绪";
}
private void btnExport_Click(object sender, EventArgs e)
{
ExportResults();
}
private bool ValidateInput()
{
// 验证主机地址
string target = txtTarget.Text.Trim();
if (string.IsNullOrEmpty(target))
{
MessageBox.Show("请输入主机地址!", "输入错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtTarget.Focus();
return false;
}
// 验证端口范围
if (numStartPort.Value > numEndPort.Value)
{
MessageBox.Show("起始端口不能大于结束端口!", "输入错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
numStartPort.Focus();
return false;
}
return true;
}
private async void StartScan()
{
try
{
cancellationTokenSource = new CancellationTokenSource();
UpdateUI(true);
string target = txtTarget.Text.Trim();
int startPort = (int)numStartPort.Value;
int endPort = (int)numEndPort.Value;
int threadCount = (int)numThreads.Value;
int timeout = (int)numTimeout.Value;
totalPorts = endPort - startPort + 1;
scannedPorts = 0;
openPorts = 0;
progressBar.Maximum = totalPorts;
progressBar.Value = 0;
lblStatus.Text = $"正在扫描 {target}:{startPort}-{endPort}...";
// 首先进行ping测试
bool isHostAlive = await PingHost(target);
if (!isHostAlive)
{
AddResult($"警告: 主机 {target} 可能无法访问或不响应ping请求", Color.Orange);
}
// 开始端口扫描
await ScanPortsAsync(target, startPort, endPort, threadCount, timeout, cancellationTokenSource.Token);
if (!cancellationTokenSource.Token.IsCancellationRequested)
{
lblStatus.Text = $"扫描完成! 共扫描 {totalPorts} 个端口,发现 {openPorts} 个开放端口";
AddResult("=== 扫描完成 ===", Color.Green);
}
}
catch (OperationCanceledException)
{
lblStatus.Text = "扫描已取消";
AddResult("=== 扫描已取消 ===", Color.Red);
}
catch (Exception ex)
{
lblStatus.Text = "扫描出错";
AddResult($"错误: {ex.Message}", Color.Red);
}
finally
{
UpdateUI(false);
}
}
private void StopScan()
{
cancellationTokenSource?.Cancel();
}
private async Task<bool> PingHost(string target)
{
try
{
using (var ping = new Ping())
{
var reply = await ping.SendPingAsync(target, 3000);
return reply.Status == IPStatus.Success;
}
}
catch
{
return false;
}
}
private async Task ScanPortsAsync(string target, int startPort, int endPort, int threadCount, int timeout, CancellationToken cancellationToken)
{
var semaphore = new SemaphoreSlim(threadCount, threadCount);
var tasks = new List<Task>();
for (int port = startPort; port <= endPort; port++)
{
if (cancellationToken.IsCancellationRequested)
break;
int currentPort = port;
var task = Task.Run(async () =>
{
await semaphore.WaitAsync(cancellationToken);
try
{
await ScanPortAsync(target, currentPort, timeout, cancellationToken);
}
finally
{
semaphore.Release();
}
}, cancellationToken);
tasks.Add(task);
}
await Task.WhenAll(tasks);
}
private async Task ScanPortAsync(string target, int port, int timeout, CancellationToken cancellationToken)
{
try
{
using (var client = new TcpClient())
{
cancellationToken.ThrowIfCancellationRequested();
var connectTask = client.ConnectAsync(target, port);
var timeoutTask = Task.Delay(timeout, cancellationToken);
var completedTask = await Task.WhenAny(connectTask, timeoutTask);
if (completedTask == connectTask && client.Connected)
{
string service = GetServiceName(port);
var result = new ScanResult
{
Host = target,
Port = port,
IsOpen = true,
Service = service,
ScanTime = DateTime.Now
};
lock (lockObject)
{
scanResults.Add(result);
openPorts++;
}
Invoke(new Action(() =>
{
AddResult($"端口 {port} 开放 - {service}", Color.Green);
}));
}
}
}
catch (OperationCanceledException)
{
throw;
}
catch
{
// 端口关闭或连接失败
}
finally
{
lock (lockObject)
{
scannedPorts++;
}
Invoke(new Action(() =>
{
progressBar.Value = scannedPorts;
lblStatus.Text = $"正在扫描... ({scannedPorts}/{totalPorts}) 已发现 {openPorts} 个开放端口";
}));
}
}
private string GetServiceName(int port)
{
var commonPorts = new Dictionary<int, string>
{
{21, "FTP"},
{22, "SSH"},
{23, "Telnet"},
{25, "SMTP"},
{53, "DNS"},
{80, "HTTP"},
{110, "POP3"},
{135, "RPC"},
{139, "NetBIOS"},
{143, "IMAP"},
{443, "HTTPS"},
{445, "SMB"},
{993, "IMAPS"},
{995, "POP3S"},
{1433, "MSSQL"},
{1521, "Oracle"},
{3306, "MySQL"},
{3389, "RDP"},
{5432, "PostgreSQL"},
{5900, "VNC"},
{6379, "Redis"},
{8080, "HTTP-Alt"},
{8443, "HTTPS-Alt"}
};
return commonPorts.ContainsKey(port) ? commonPorts[port] : "Unknown";
}
private void AddResult(string message, Color color)
{
if (InvokeRequired)
{
Invoke(new Action(() => AddResult(message, color)));
return;
}
lstResults.Items.Add($"[{DateTime.Now:HH:mm:ss}] {message}");
// 滚动到最后一项
if (lstResults.Items.Count > 0)
{
lstResults.TopIndex = lstResults.Items.Count - 1;
}
}
private void UpdateUI(bool isScanning)
{
btnScan.Enabled = !isScanning;
btnStop.Enabled = isScanning;
txtTarget.Enabled = !isScanning;
numStartPort.Enabled = !isScanning;
numEndPort.Enabled = !isScanning;
numThreads.Enabled = !isScanning;
numTimeout.Enabled = !isScanning;
}
private void ExportResults()
{
if (scanResults.Count == 0)
{
MessageBox.Show("没有扫描结果可导出!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
using (var saveDialog = new SaveFileDialog())
{
saveDialog.Filter = "文本文件 (*.txt)|*.txt|CSV文件 (*.csv)|*.csv";
saveDialog.FileName = $"PortScan_{DateTime.Now:yyyyMMdd_HHmmss}";
if (saveDialog.ShowDialog() == DialogResult.OK)
{
try
{
var content = new StringBuilder();
if (saveDialog.FilterIndex == 1) // TXT格式
{
content.AppendLine("端口扫描结果");
content.AppendLine("================");
content.AppendLine($"扫描时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
content.AppendLine($"目标主机: {txtTarget.Text}");
content.AppendLine($"端口范围: {numStartPort.Value}-{numEndPort.Value}");
content.AppendLine($"开放端口数: {openPorts}");
content.AppendLine();
foreach (var result in scanResults)
{
if (result.IsOpen)
{
content.AppendLine($"{result.Host}:{result.Port} - {result.Service}");
}
}
}
else // CSV格式
{
content.AppendLine("主机,端口,状态,服务,扫描时间");
foreach (var result in scanResults)
{
if (result.IsOpen)
{
content.AppendLine($"{result.Host},{result.Port},开放,{result.Service},{result.ScanTime:yyyy-MM-dd HH:mm:ss}");
}
}
}
File.WriteAllText(saveDialog.FileName, content.ToString(), Encoding.UTF8);
MessageBox.Show($"结果已导出到: {saveDialog.FileName}", "导出成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"导出失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
cancellationTokenSource?.Cancel();
}
}
public class ScanResult
{
public string Host { get; set; }
public int Port { get; set; }
public bool IsOpen { get; set; }
public string Service { get; set; }
public DateTime ScanTime { get; set; }
}
}
C#// 根据CPU核心数动态调整默认线程数
int defaultThreadCount = Environment.ProcessorCount * 4;
numThreads.Value = Math.Min(defaultThreadCount, 100); // 最大不超过100
C#// 根据网络环境动态调整超时时间
private async Task<int> GetOptimalTimeout(string target)
{
try
{
using (var ping = new Ping())
{
var reply = await ping.SendPingAsync(target, 1000);
if (reply.Status == IPStatus.Success)
{
// 根据ping延迟调整超时时间
return Math.Max((int)reply.RoundtripTime * 3, 1000);
}
}
}
catch { }
return 3000; // 默认3秒超时
}
解决方案: 正确使用using
语句和Dispose
模式
解决方案: 使用async/await
和Invoke
正确处理跨线程操作
解决方案: 合理设置并发数和超时时间,避免网络拥塞
C#AppPortScanner/
├── Form1.cs // 主窗体逻辑
├── Form1.Designer.cs // 窗体设计器代码
├── ScanResult.cs // 扫描结果数据模型
├── Program.cs // 程序入口点
└── App.config // 应用配置文件
通过这个端口扫描器项目,我们掌握了C#网络编程的三大核心技术:
这些技术不仅适用于端口扫描,更是现代C#开发中不可或缺的技能。掌握了这些,你就能轻松处理各种网络编程挑战!
💬 互动时间:
觉得有用的话,记得分享给更多的C#同行!让我们一起在技术路上越走越远! 🚀
相关信息
通过网盘分享的文件:AppPortScan.zip 链接: https://pan.baidu.com/s/1ky5gjulEVLMoGUQRMH83Zw?pwd=nnbc 提取码: nnbc --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!