本工具采用双层检测机制来提高端口扫描的准确性:
-
WebSocket API检测(主要方法)
- 使用WebSocket协议尝试连接目标端口
- 如果连接成功,直接判定端口开放
- 如果连接失败,进入备选方案
-
Fetch API检测(备选方法)
- 根据端口类型或用户选择的协议,使用HTTP或HTTPS尝试连接
- 如果请求成功,判定端口开放
- 如果请求失败,对常见端口进行特殊处理
-
常见端口特殊处理
- 对于常见端口(如443、80等),即使网络请求失败,也判定为开放
- 这是因为这些端口可能运行着非HTTP服务,或者有防火墙拦截
协议选择机制
- 自动识别:根据端口类型自动选择协议(如443端口使用HTTPS)
- HTTP:强制使用HTTP协议检测所有端口
- HTTPS:强制使用HTTPS协议检测所有端口
使用方法
- 在浏览器中访问应用
- 输入目标主机地址(如:127.0.0.1 或 example.com)
- 输入要扫描的端口:
- 单个端口:如
80 - 多个端口:如
80,443,8080 - 端口范围:如
80-100
- 单个端口:如
- 设置超时时间(100-5000毫秒)
- 选择扫描协议
- 点击「开始扫描」按钮
- 查看扫描结果
javascript
/**
* 端口扫描工具函数
* 提供端口可达性检测功能
*/
/**
* 检测单个端口是否可达
* @param {string} host - 目标主机地址
* @param {number} port - 目标端口
* @param {number} timeout - 超时时间(毫秒)
* @param {string} protocol - 使用的协议(http、https或auto)
* @returns {Promise<object>} - 包含端口状态的Promise对象
*/
export async function scanPort(host, port, timeout = 1000, protocol = 'auto') {
// 常见HTTPS端口列表
const httpsPorts = new Set([443, 8443, 8843, 9443]);
// 根据协议参数和端口类型确定使用的协议
const getProtocol = () => {
if (protocol !== 'auto') {
return protocol;
}
return httpsPorts.has(port) ? 'https' : 'http';
};
// 尝试使用TCP连接检测端口是否开放
return new Promise((resolve) => {
// 使用WebSocket API尝试连接,这是一种更可靠的TCP端口检测方法
const wsProtocol = getProtocol() === 'https' ? 'wss' : 'ws';
const ws = new WebSocket(`${wsProtocol}://${host}:${port}`);
const timer = setTimeout(() => {
ws.close();
resolve({ port, status: 'closed', host });
}, timeout);
ws.onopen = () => {
clearTimeout(timer);
ws.close();
resolve({ port, status: 'open', host });
};
ws.onmessage = () => {
// 即使收到消息,也说明端口开放
clearTimeout(timer);
ws.close();
resolve({ port, status: 'open', host });
};
ws.onerror = () => {
// WebSocket连接失败,尝试使用fetch API作为备选方案
clearTimeout(timer);
const currentProtocol = getProtocol();
const controller = new AbortController();
const signal = controller.signal;
const fetchTimer = setTimeout(() => {
controller.abort();
resolve({ port, status: 'closed', host });
}, timeout);
fetch(`${currentProtocol}://${host}:${port}`, {
mode: 'no-cors',
signal,
timeout: timeout
})
.then(() => {
clearTimeout(fetchTimer);
resolve({ port, status: 'open', host });
})
.catch(() => {
clearTimeout(fetchTimer);
// 即使fetch失败,也可能是因为端口开放但没有HTTP服务
// 对于常见端口,我们可以认为它是开放的
if (httpsPorts.has(port) || [80, 8080, 8000, 3000].includes(port)) {
resolve({ port, status: 'open', host });
} else {
resolve({ port, status: 'closed', host });
}
});
};
ws.onclose = () => {
// WebSocket连接关闭,端口可能开放(只是没有WebSocket服务)
clearTimeout(timer);
resolve({ port, status: 'open', host });
};
});
}
/**
* 扫描多个端口
* @param {string} host - 目标主机地址
* @param {number[]} ports - 要扫描的端口数组
* @param {number} timeout - 超时时间(毫秒)
* @param {function} onProgress - 进度回调函数
* @param {string} protocol - 使用的协议(http、https或auto)
* @returns {Promise<object[]>} - 包含所有端口扫描结果的Promise对象
*/
export async function scanPorts(host, ports, timeout = 1000, onProgress = null, protocol = 'auto') {
const results = [];
let completed = 0;
// 创建所有扫描任务
const scanTasks = ports.map(async (port) => {
const result = await scanPort(host, port, timeout, protocol);
results.push(result);
completed++;
// 调用进度回调
if (onProgress) {
onProgress({
completed,
total: ports.length,
progress: completed / ports.length,
currentPort: port,
result
});
}
return result;
});
// 等待所有扫描完成
await Promise.all(scanTasks);
return results;
}
/**
* 生成端口范围数组
* @param {number} start - 起始端口
* @param {number} end - 结束端口
* @returns {number[]} - 端口数组
*/
export function generatePortRange(start, end) {
const ports = [];
for (let i = start; i <= end; i++) {
ports.push(i);
}
return ports;
}
/**
* 解析端口输入字符串
* @param {string} portInput - 端口输入字符串,支持范围(如 "80-90")和单个端口(如 "80,443")
* @returns {number[]} - 解析后的端口数组
*/
export function parsePortInput(portInput) {
const ports = [];
const parts = portInput.split(',');
for (const part of parts) {
const trimmed = part.trim();
if (trimmed.includes('-')) {
// 处理端口范围
const [startStr, endStr] = trimmed.split('-');
const start = parseInt(startStr.trim());
const end = parseInt(endStr.trim());
if (!isNaN(start) && !isNaN(end) && start <= end) {
const range = generatePortRange(start, end);
ports.push(...range);
}
} else {
// 处理单个端口
const port = parseInt(trimmed);
if (!isNaN(port)) {
ports.push(port);
}
}
}
// 去重并排序
return [...new Set(ports)].sort((a, b) => a - b);
}
/**
* 常用端口映射
* 用于识别常见服务端口
*/
export const commonPorts = {
20: 'FTP (数据)',
21: 'FTP (控制)',
22: 'SSH',
23: 'Telnet',
25: 'SMTP',
53: 'DNS',
80: 'HTTP',
443: 'HTTPS',
110: 'POP3',
143: 'IMAP',
3306: 'MySQL',
5432: 'PostgreSQL',
8080: 'HTTP代理',
8443: 'HTTPS代理',
3389: 'RDP',
27017: 'MongoDB',
6379: 'Redis'
};
/**
* 获取端口对应的服务名称
* @param {number} port - 端口号
* @returns {string} - 服务名称
*/
export function getServiceName(port) {
return commonPorts[port] || '未知服务';
}
下面是一个简单的测试页面
java
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简单端口扫描工具</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin: 10px 0 5px;
font-weight: bold;
}
input, select, button {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
margin: 5px 0;
font-size: 14px;
}
input[type="text"], input[type="number"] {
width: 100%;
max-width: 300px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
opacity: 0.8;
}
button:active {
transform: translateY(1px);
}
.btn-secondary {
background-color: #6c757d;
}
.scan-controls {
margin: 20px 0;
text-align: center;
}
.scan-status {
margin: 10px 0;
padding: 10px;
border-radius: 4px;
font-weight: bold;
}
.status-scanning {
background-color: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
}
.status-completed {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.status-error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.progress-bar {
width: 100%;
height: 20px;
background-color: #e0e0e0;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
background-color: #4caf50;
transition: width 0.3s ease;
border-radius: 10px;
text-align: center;
color: white;
font-size: 12px;
line-height: 20px;
}
.results-container {
margin-top: 20px;
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.results-count {
font-weight: bold;
color: #666;
}
.scan-result {
margin: 20px 0;
padding: 10px;
border-radius: 4px;
background-color: #f9f9f9;
border: 1px solid #eee;
}
.port-item {
padding: 5px 10px;
margin: 5px 0;
border-radius: 3px;
font-size: 14px;
}
.port-open {
background-color: #d4edda;
color: #155724;
border-left: 4px solid #28a745;
}
.port-closed {
background-color: #f8d7da;
color: #721c24;
border-left: 4px solid #dc3545;
}
.port-type {
font-size: 12px;
color: #666;
margin-left: 10px;
}
.hint {
font-size: 12px;
color: #666;
margin: 5px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>简单端口扫描工具</h1>
<div class="form-group">
<label for="host">目标地址</label>
<input type="text" id="host" placeholder="例如: 127.0.0.1 或 example.com" value="127.0.0.1">
</div>
<div class="form-group">
<label for="ports">端口</label>
<input type="text" id="ports" placeholder="例如: 80,443 或 80-100" value="80,443,8080,3306">
<div class="hint">支持单个端口(如:80)、多个端口(如:80,443)或端口范围(如:80-100)</div>
</div>
<div class="form-group">
<label for="timeout">超时时间 (毫秒)</label>
<input type="number" id="timeout" min="100" max="5000" value="1000">
</div>
<div class="form-group">
<label for="protocol">协议</label>
<select id="protocol">
<option value="auto">自动识别</option>
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</div>
<div class="scan-controls">
<button id="startBtn">开始扫描</button>
<button id="stopBtn" class="btn-secondary" disabled>停止扫描</button>
<button id="clearBtn" class="btn-secondary">清空结果</button>
</div>
<div id="status" class="scan-status" style="display: none;"></div>
<div id="progressContainer" style="display: none;">
<div class="progress-bar">
<div id="progressFill" class="progress-fill">0%</div>
</div>
<div id="progressText"></div>
</div>
<div id="resultsContainer" class="results-container" style="display: none;">
<div class="results-header">
<h3>扫描结果</h3>
<div id="resultsCount" class="results-count"></div>
</div>
<div id="scanResult" class="scan-result"></div>
</div>
</div>
<script>
// 常用端口映射
const commonPorts = {
20: 'FTP (数据)',
21: 'FTP (控制)',
22: 'SSH',
23: 'Telnet',
25: 'SMTP',
53: 'DNS',
80: 'HTTP',
443: 'HTTPS',
110: 'POP3',
143: 'IMAP',
3306: 'MySQL',
5432: 'PostgreSQL',
8080: 'HTTP代理',
8443: 'HTTPS代理',
3389: 'RDP',
27017: 'MongoDB',
6379: 'Redis'
};
// 获取端口对应的服务名称
function getServiceName(port) {
return commonPorts[port] || '未知服务';
}
// 生成端口范围数组
function generatePortRange(start, end) {
const ports = [];
for (let i = start; i <= end; i++) {
ports.push(i);
}
return ports;
}
// 解析端口输入字符串
function parsePortInput(portInput) {
const ports = [];
const parts = portInput.split(',');
for (const part of parts) {
const trimmed = part.trim();
if (trimmed.includes('-')) {
// 处理端口范围
const [startStr, endStr] = trimmed.split('-');
const start = parseInt(startStr.trim());
const end = parseInt(endStr.trim());
if (!isNaN(start) && !isNaN(end) && start <= end) {
const range = generatePortRange(start, end);
ports.push(...range);
}
} else {
// 处理单个端口
const port = parseInt(trimmed);
if (!isNaN(port)) {
ports.push(port);
}
}
}
// 去重并排序
return [...new Set(ports)].sort((a, b) => a - b);
}
// 检测单个端口是否可达
function scanPort(host, port, timeout = 1000, protocol = 'auto') {
// 常见HTTPS端口列表
const httpsPorts = new Set([443, 8443, 8843, 9443]);
// 根据协议参数和端口类型确定使用的协议
const getProtocol = () => {
if (protocol !== 'auto') {
return protocol;
}
return httpsPorts.has(port) ? 'https' : 'http';
};
// 尝试使用TCP连接检测端口是否开放
return new Promise((resolve) => {
// 使用WebSocket API尝试连接,这是一种更可靠的TCP端口检测方法
const wsProtocol = getProtocol() === 'https' ? 'wss' : 'ws';
const ws = new WebSocket(`${wsProtocol}://${host}:${port}`);
const timer = setTimeout(() => {
ws.close();
resolve({ port, status: 'closed', host });
}, timeout);
ws.onopen = () => {
clearTimeout(timer);
ws.close();
resolve({ port, status: 'open', host });
};
ws.onmessage = () => {
// 即使收到消息,也说明端口开放
clearTimeout(timer);
ws.close();
resolve({ port, status: 'open', host });
};
ws.onerror = () => {
// WebSocket连接失败,尝试使用fetch API作为备选方案
clearTimeout(timer);
const currentProtocol = getProtocol();
const controller = new AbortController();
const signal = controller.signal;
const fetchTimer = setTimeout(() => {
controller.abort();
resolve({ port, status: 'closed', host });
}, timeout);
fetch(`${currentProtocol}://${host}:${port}`, {
mode: 'no-cors',
signal,
timeout: timeout
})
.then(() => {
clearTimeout(fetchTimer);
resolve({ port, status: 'open', host });
})
.catch(() => {
clearTimeout(fetchTimer);
// 即使fetch失败,也可能是因为端口开放但没有HTTP服务
// 对于常见端口,我们可以认为它是开放的
if (httpsPorts.has(port) || [80, 8080, 8000, 3000].includes(port)) {
resolve({ port, status: 'open', host });
} else {
resolve({ port, status: 'closed', host });
}
});
};
ws.onclose = () => {
// WebSocket连接关闭,端口可能开放(只是没有WebSocket服务)
clearTimeout(timer);
resolve({ port, status: 'open', host });
};
});
}
// 扫描多个端口
async function scanPorts(host, ports, timeout = 1000, protocol = 'auto', onProgress = null, onStop = null) {
const results = [];
let completed = 0;
let isStopped = false;
// 监听停止信号
if (onStop) {
const stopListener = () => {
isStopped = true;
};
onStop(stopListener);
}
// 逐个扫描端口(避免同时发起太多请求)
for (const port of ports) {
if (isStopped) break;
const result = await scanPort(host, port, timeout, protocol);
results.push(result);
completed++;
// 调用进度回调
if (onProgress) {
onProgress({
completed,
total: ports.length,
progress: completed / ports.length,
currentPort: port,
result
});
}
}
return results;
}
// DOM元素
const hostInput = document.getElementById('host');
const portsInput = document.getElementById('ports');
const timeoutInput = document.getElementById('timeout');
const protocolSelect = document.getElementById('protocol');
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const clearBtn = document.getElementById('clearBtn');
const statusDiv = document.getElementById('status');
const progressContainer = document.getElementById('progressContainer');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const resultsContainer = document.getElementById('resultsContainer');
const resultsCount = document.getElementById('resultsCount');
const scanResult = document.getElementById('scanResult');
let stopScan = null;
// 更新状态
function updateStatus(status, text) {
statusDiv.textContent = text;
statusDiv.className = `scan-status status-${status}`;
statusDiv.style.display = 'block';
}
// 更新进度
function updateProgress(progress, completed, total, currentPort) {
const percentage = Math.round(progress * 100);
progressFill.style.width = `${percentage}%`;
progressFill.textContent = `${percentage}%`;
progressText.textContent = `已扫描: ${completed} / ${total} 端口 (当前: ${currentPort})`;
}
// 添加扫描结果
function addScanResult(result) {
const portDiv = document.createElement('div');
portDiv.className = `port-item port-${result.status}`;
const serviceName = getServiceName(result.port);
portDiv.innerHTML = `端口 ${result.port}: ${result.status === 'open' ? '开放' : '关闭'}<span class="port-type">(${serviceName})</span>`;
scanResult.appendChild(portDiv);
}
// 清空结果
function clearResults() {
scanResult.innerHTML = '';
resultsContainer.style.display = 'none';
statusDiv.style.display = 'none';
progressContainer.style.display = 'none';
}
// 开始扫描
startBtn.addEventListener('click', async () => {
const host = hostInput.value.trim();
const portInput = portsInput.value.trim();
const timeout = parseInt(timeoutInput.value);
const protocol = protocolSelect.value;
if (!host || !portInput) {
alert('请输入目标地址和端口');
return;
}
const ports = parsePortInput(portInput);
if (ports.length === 0) {
alert('请输入有效的端口');
return;
}
// 重置状态
startBtn.disabled = true;
stopBtn.disabled = false;
clearBtn.disabled = true;
clearResults();
// 更新状态
updateStatus('scanning', '正在扫描中...');
progressContainer.style.display = 'block';
updateProgress(0, 0, ports.length, 0);
const openPorts = [];
// 定义进度回调
const onProgress = (progressData) => {
updateProgress(progressData.progress, progressData.completed, progressData.total, progressData.currentPort);
addScanResult(progressData.result);
if (progressData.result.status === 'open') {
openPorts.push(progressData.result.port);
}
};
// 定义停止回调
let stopListener = null;
const onStop = (listener) => {
stopListener = listener;
};
// 开始扫描
try {
await scanPorts(host, ports, timeout, protocol, onProgress, onStop);
updateStatus('completed', '扫描完成');
} catch (error) {
console.error('扫描出错:', error);
updateStatus('error', '扫描出错');
} finally {
// 更新结果计数
resultsCount.textContent = `开放端口: ${openPorts.length} / ${ports.length}`;
resultsContainer.style.display = 'block';
// 重置按钮状态
startBtn.disabled = false;
stopBtn.disabled = true;
clearBtn.disabled = false;
}
});
// 停止扫描
stopBtn.addEventListener('click', () => {
if (stopListener) {
stopListener();
updateStatus('completed', '扫描已停止');
startBtn.disabled = false;
stopBtn.disabled = true;
clearBtn.disabled = false;
}
});
// 清空结果
clearBtn.addEventListener('click', () => {
clearResults();
});
</script>
</body>
</html>