使用C++实现的TCP端口开放检测工具,支持单个端口、多个端口和端口范围的检测,具有简洁的命令行界面和详细的扫描结果输出。
源代码
cpp
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <cstring>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <conio.h>
#include <thread>
#include <mutex>
#include <atomic>
#include <queue>
#include <Windows.h>
#include <iphlpapi.h>
#include <algorithm>
#include <iomanip>
#include <map>
#include <set>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")
// 最大端口号
const int MAX_PORT = 65535;
// 默认超时时间(毫秒)
const int DEFAULT_TIMEOUT = 2000;
// 最大线程数
const int MAX_THREADS = 100;
// 端口扫描结果结构体
struct PortResult {
int port;
bool isOpen;
std::string serviceName;
std::string banner;
};
// 全局变量
std::mutex g_mutex;
std::atomic<int> g_activeThreads(0);
std::atomic<int> g_scannedPorts(0);
std::atomic<int> g_openPorts(0);
std::queue<PortResult> g_results;
std::map<int, std::string> g_commonPorts = {
{7, "Echo"},
{20, "FTP-DATA"},
{21, "FTP"},
{22, "SSH"},
{23, "Telnet"},
{25, "SMTP"},
{53, "DNS"},
{67, "DHCP"},
{68, "DHCP"},
{69, "TFTP"},
{80, "HTTP"},
{110, "POP3"},
{119, "NNTP"},
{123, "NTP"},
{137, "NetBIOS"},
{138, "NetBIOS"},
{139, "NetBIOS"},
{143, "IMAP"},
{161, "SNMP"},
{162, "SNMP"},
{389, "LDAP"},
{443, "HTTPS"},
{445, "SMB"},
{465, "SMTPS"},
{514, "Syslog"},
{587, "SMTP"},
{636, "LDAPS"},
{993, "IMAPS"},
{995, "POP3S"},
{1080, "SOCKS"},
{1433, "MSSQL"},
{1521, "Oracle"},
{1723, "PPTP"},
{3306, "MySQL"},
{3389, "RDP"},
{5432, "PostgreSQL"},
{5900, "VNC"},
{8080, "HTTP-Proxy"},
{8443, "HTTPS-Alt"}
};
// 获取服务名称
std::string GetServiceName(int port, const std::string& protocol = "tcp") {
std::string serviceName = "Unknown";
// 检查常见端口
auto it = g_commonPorts.find(port);
if (it != g_commonPorts.end()) {
serviceName = it->second;
}
// 尝试通过系统API获取服务名称
struct servent* serv = getservbyport(htons(port), protocol.c_str());
if (serv != nullptr) {
serviceName = serv->s_name;
}
return serviceName;
}
// 尝试获取Banner
std::string GetBanner(SOCKET sock) {
char buffer[1024] = {0};
int bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (bytesReceived > 0) {
return std::string(buffer, bytesReceived);
}
return "";
}
// 扫描单个端口
void ScanPort(const std::string& target, int port, int timeout) {
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
return;
}
// 设置超时
DWORD timeoutVal = timeout;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeoutVal, sizeof(timeoutVal));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeoutVal, sizeof(timeoutVal));
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
inet_pton(AF_INET, target.c_str(), &server.sin_addr);
// 尝试连接
int result = connect(sock, (sockaddr*)&server, sizeof(server));
bool isOpen = (result != SOCKET_ERROR);
PortResult res;
res.port = port;
res.isOpen = isOpen;
res.serviceName = isOpen ? GetServiceName(port) : "";
res.banner = "";
if (isOpen) {
// 尝试获取Banner
res.banner = GetBanner(sock);
g_openPorts++;
}
// 保存结果
{
std::lock_guard<std::mutex> lock(g_mutex);
g_results.push(res);
}
closesocket(sock);
g_scannedPorts++;
g_activeThreads--;
}
// 工作线程函数
void WorkerThread(const std::string& target, const std::vector<int>& ports, int timeout) {
while (!ports.empty()) {
int port = 0;
{
// 从队列中获取端口
std::lock_guard<std::mutex> lock(g_mutex);
if (ports.empty()) break;
port = ports.back();
ports.pop_back();
}
g_activeThreads++;
ScanPort(target, port, timeout);
}
}
// 解析端口字符串
std::vector<int> ParsePorts(const std::string& portStr) {
std::vector<int> ports;
std::istringstream ss(portStr);
std::string token;
while (std::getline(ss, token, ',')) {
// 检查是否是端口范围
size_t dashPos = token.find('-');
if (dashPos != std::string::npos) {
int start = std::stoi(token.substr(0, dashPos));
int end = std::stoi(token.substr(dashPos + 1));
for (int p = start; p <= end; p++) {
if (p >= 1 && p <= MAX_PORT) {
ports.push_back(p);
}
}
} else {
// 单个端口
int port = std::stoi(token);
if (port >= 1 && port <= MAX_PORT) {
ports.push_back(port);
}
}
}
// 去重并排序
std::sort(ports.begin(), ports.end());
ports.erase(std::unique(ports.begin(), ports.end()), ports.end());
return ports;
}
// 显示扫描进度
void DisplayProgress(int totalPorts) {
std::cout << "\r扫描进度: " << g_scannedPorts << "/" << totalPorts
<< " (" << std::fixed << std::setprecision(1)
<< (static_cast<float>(g_scannedPorts) / totalPorts * 100) << "%)"
<< " | 开放端口: " << g_openPorts
<< " | 活动线程: " << g_activeThreads << std::flush;
}
// 显示扫描结果
void DisplayResults() {
std::cout << "\n\n扫描结果:\n";
std::cout << "========================================\n";
std::cout << std::left << std::setw(8) << "端口"
<< std::setw(20) << "状态"
<< std::setw(20) << "服务"
<< "Banner\n";
std::cout << "----------------------------------------\n";
// 收集所有结果
std::vector<PortResult> allResults;
{
std::lock_guard<std::mutex> lock(g_mutex);
while (!g_results.empty()) {
allResults.push_back(g_results.front());
g_results.pop();
}
}
// 按端口号排序
std::sort(allResults.begin(), allResults.end(),
const PortResult& a, const PortResult& b { return a.port < b.port; });
// 显示结果
for (const auto& res : allResults) {
std::cout << std::left << std::setw(8) << res.port
<< std::setw(20) << (res.isOpen ? "开放" : "关闭")
<< std::setw(20) << res.serviceName;
if (res.isOpen && !res.banner.empty()) {
// 截断过长的banner
std::string displayBanner = res.banner;
if (displayBanner.length() > 50) {
displayBanner = displayBanner.substr(0, 47) + "...";
}
std::cout << displayBanner;
}
std::cout << "\n";
}
std::cout << "========================================\n";
std::cout << "扫描完成! 共扫描 " << allResults.size() << " 个端口, 发现 "
<< g_openPorts << " 个开放端口\n";
}
// 解析IP地址
std::string ResolveHostname(const std::string& hostname) {
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
if (getaddrinfo(hostname.c_str(), NULL, &hints, &res) != 0) {
return "";
}
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &((struct sockaddr_in*)res->ai_addr)->sin_addr, ip, sizeof(ip));
freeaddrinfo(res);
return std::string(ip);
}
// 检查IP是否有效
bool IsValidIP(const std::string& ip) {
struct sockaddr_in sa;
return inet_pton(AF_INET, ip.c_str(), &sa.sin_addr) == 1;
}
// 显示帮助信息
void ShowHelp() {
std::cout << "TCP端口扫描工具 v1.0\n";
std::cout << "用法: portscanner [选项] <目标> <端口>\n\n";
std::cout << "选项:\n";
std::cout << " -t <线程数> 设置扫描线程数 (默认: 10)\n";
std::cout << " -o <超时> 设置连接超时时间(毫秒) (默认: 2000)\n";
std::cout << " -h 显示帮助信息\n\n";
std::cout << "端口格式:\n";
std::cout << " 单个端口: 80\n";
std::cout << " 多个端口: 80,443,8080\n";
std::cout << " 端口范围: 1-1000\n";
std::cout << " 组合: 21,22,80,443,8000-8100\n\n";
std::cout << "示例:\n";
std::cout << " portscanner 192.168.1.1 80\n";
std::cout << " portscanner example.com 20-100\n";
std::cout << " portscanner -t 50 -o 1000 10.0.0.1 1-1024\n";
}
// 主函数
int main(int argc, char* argv[]) {
// 默认参数
int threadCount = 10;
int timeout = DEFAULT_TIMEOUT;
std::string target;
std::string portStr;
// 解析命令行参数
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-t" && i + 1 < argc) {
threadCount = std::stoi(argv[++i]);
if (threadCount < 1) threadCount = 1;
if (threadCount > MAX_THREADS) threadCount = MAX_THREADS;
} else if (arg == "-o" && i + 1 < argc) {
timeout = std::stoi(argv[++i]);
if (timeout < 100) timeout = 100;
if (timeout > 10000) timeout = 10000;
} else if (arg == "-h") {
ShowHelp();
return 0;
} else if (target.empty()) {
target = arg;
} else if (portStr.empty()) {
portStr = arg;
}
}
// 检查必要参数
if (target.empty() || portStr.empty()) {
std::cout << "错误: 缺少目标或端口参数!\n";
ShowHelp();
return 1;
}
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup失败!\n";
return 1;
}
// 解析目标地址
std::string ip = target;
if (!IsValidIP(target)) {
std::cout << "正在解析域名: " << target << "...\n";
ip = ResolveHostname(target);
if (ip.empty()) {
std::cerr << "无法解析域名: " << target << "\n";
WSACleanup();
return 1;
}
std::cout << "解析结果: " << target << " -> " << ip << "\n";
}
// 解析端口
std::vector<int> ports = ParsePorts(portStr);
if (ports.empty()) {
std::cerr << "错误: 无效的端口格式!\n";
WSACleanup();
return 1;
}
// 限制扫描端口数量
if (ports.size() > 10000) {
std::cout << "警告: 扫描端口数量过多 (" << ports.size() << "), 建议减少范围\n";
std::cout << "是否继续? (y/n): ";
char choice = _getch();
std::cout << "\n";
if (choice != 'y' && choice != 'Y') {
WSACleanup();
return 0;
}
}
std::cout << "开始扫描 " << ip << " 的 " << ports.size() << " 个端口...\n";
std::cout << "线程数: " << threadCount << ", 超时: " << timeout << "ms\n";
// 创建线程池
std::vector<std::thread> threads;
std::vector<std::vector<int>> threadPorts(threadCount);
// 分配端口给线程
for (size_t i = 0; i < ports.size(); i++) {
threadPorts[i % threadCount].push_back(ports[i]);
}
// 启动线程
auto startTime = std::chrono::high_resolution_clock::now();
for (int i = 0; i < threadCount; i++) {
threads.emplace_back(WorkerThread, ip, std::ref(threadPorts[i]), timeout);
}
// 显示进度
while (g_scannedPorts < ports.size()) {
DisplayProgress(ports.size());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// 等待所有线程完成
for (auto& t : threads) {
if (t.joinable()) t.join();
}
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
// 显示结果
DisplayResults();
std::cout << "扫描耗时: " << duration.count() << "ms\n";
// 清理
WSACleanup();
return 0;
}
功能特点
-
灵活的端口扫描:
- 支持单个端口扫描(如80)
- 支持多个端口扫描(如80,443,8080)
- 支持端口范围扫描(如1-1000)
- 支持混合格式(如21,22,80,443,8000-8100)
-
多线程扫描:
- 可配置的线程数量(默认10线程)
- 线程池管理,高效利用系统资源
- 实时显示扫描进度
-
详细的结果展示:
- 端口开放状态(开放/关闭)
- 服务名称识别(基于常见端口和服务数据库)
- Banner抓取(尝试获取服务标识)
- 扫描统计信息(耗时、扫描端口数、开放端口数)
-
网络诊断功能:
- 域名解析(自动将域名转换为IP地址)
- 自定义连接超时时间(默认2000ms)
- 详细的错误处理和帮助信息
参考代码 socket-tcp简易端口开放检测工具 www.youwenfan.com/contentcst/122324.html
使用方法
命令行参数
portscanner [选项] <目标> <端口>
选项:
-t <线程数> 设置扫描线程数 (默认: 10)
-o <超时> 设置连接超时时间(毫秒) (默认: 2000)
-h 显示帮助信息
端口格式:
单个端口: 80
多个端口: 80,443,8080
端口范围: 1-1000
组合: 21,22,80,443,8000-8100
使用示例
-
扫描单个端口:
portscanner 192.168.1.1 80 -
扫描多个端口:
portscanner example.com 80,443,8080 -
扫描端口范围:
portscanner 10.0.0.1 1-1024 -
自定义线程数和超时时间:
portscanner -t 50 -o 1000 192.168.1.1 1-65535
技术实现细节
1. 端口扫描核心逻辑
cpp
void ScanPort(const std::string& target, int port, int timeout) {
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 设置超时
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, ...);
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, ...);
// 创建套接字地址结构
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
inet_pton(AF_INET, target.c_str(), &server.sin_addr);
// 尝试连接
int result = connect(sock, (sockaddr*)&server, sizeof(server));
bool isOpen = (result != SOCKET_ERROR);
// 获取服务名称和Banner
if (isOpen) {
serviceName = GetServiceName(port);
banner = GetBanner(sock);
}
// 保存结果
g_results.push({port, isOpen, serviceName, banner});
closesocket(sock);
}
2. 多线程管理
cpp
void WorkerThread(const std::string& target, const std::vector<int>& ports, int timeout) {
while (!ports.empty()) {
int port = ports.back();
ports.pop_back();
ScanPort(target, port, timeout);
}
}
// 主函数中创建线程池
for (int i = 0; i < threadCount; i++) {
threads.emplace_back(WorkerThread, ip, std::ref(threadPorts[i]), timeout);
}
3. 服务识别与Banner抓取
cpp
std::string GetServiceName(int port, const std::string& protocol) {
// 检查常见端口映射
auto it = g_commonPorts.find(port);
if (it != g_commonPorts.end()) {
return it->second;
}
// 使用系统API获取服务名称
struct servent* serv = getservbyport(htons(port), protocol.c_str());
if (serv != nullptr) {
return serv->s_name;
}
return "Unknown";
}
std::string GetBanner(SOCKET sock) {
char buffer[1024] = {0};
int bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (bytesReceived > 0) {
return std::string(buffer, bytesReceived);
}
return "";
}
4. 进度显示与结果汇总
cpp
void DisplayProgress(int totalPorts) {
std::cout << "\r扫描进度: " << g_scannedPorts << "/" << totalPorts
<< " (" << (static_cast<float>(g_scannedPorts)/totalPorts*100) << "%)"
<< " | 开放端口: " << g_openPorts
<< " | 活动线程: " << g_activeThreads << std::flush;
}
void DisplayResults() {
// 收集所有结果并按端口排序
std::vector<PortResult> allResults;
// ... 收集结果 ...
// 显示表格形式的结果
std::cout << std::left << std::setw(8) << "端口"
<< std::setw(20) << "状态"
<< std::setw(20) << "服务"
<< "Banner\n";
// ... 显示每个端口的结果 ...
}
编译与使用
Windows 编译
- 使用Visual Studio创建控制台应用程序项目
- 复制源代码到项目中
- 配置项目属性:
- 链接器 → 输入 → 附加依赖项:
ws2_32.lib;iphlpapi.lib - C/C++ → 预处理器 → 预处理器定义:
_CRT_SECURE_NO_WARNINGS
- 链接器 → 输入 → 附加依赖项:
- 编译并运行
Linux 编译(使用Wine兼容层)
bash
# 安装必要的库
sudo apt-get install g++ mingw-w64 wine
# 编译Windows可执行文件
i686-w64-mingw32-g++ -o portscanner.exe portscanner.cpp -lws2_32 -liphlpapi -static
# 运行(需要Wine)
wine portscanner.exe 127.0.0.1 1-1024
Linux 原生版本
cpp
// 替换Windows特有的头文件和函数
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#define SOCKET int
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket close
#endif
// 初始化网络库
#ifdef _WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#else
// 不需要特殊初始化
#endif
应用场景
-
网络安全评估:
- 检查系统开放的端口和服务
- 识别潜在的安全风险
- 验证防火墙配置
-
网络故障排查:
- 确认服务是否在预期端口运行
- 诊断网络连接问题
- 验证网络设备配置
-
系统管理员日常维护:
- 定期检查服务器端口状态
- 监控服务可用性
- 文档化网络服务
-
渗透测试:
- 侦察目标系统开放的服务
- 识别易受攻击的服务
- 规划后续攻击向量