前端使用JS实现端口探活

本工具采用双层检测机制来提高端口扫描的准确性:

  1. WebSocket API检测(主要方法)

    • 使用WebSocket协议尝试连接目标端口
    • 如果连接成功,直接判定端口开放
    • 如果连接失败,进入备选方案
  2. Fetch API检测(备选方法)

    • 根据端口类型或用户选择的协议,使用HTTP或HTTPS尝试连接
    • 如果请求成功,判定端口开放
    • 如果请求失败,对常见端口进行特殊处理
  3. 常见端口特殊处理

    • 对于常见端口(如443、80等),即使网络请求失败,也判定为开放
    • 这是因为这些端口可能运行着非HTTP服务,或者有防火墙拦截

协议选择机制

  • 自动识别:根据端口类型自动选择协议(如443端口使用HTTPS)
  • HTTP:强制使用HTTP协议检测所有端口
  • HTTPS:强制使用HTTPS协议检测所有端口

使用方法

  1. 在浏览器中访问应用
  2. 输入目标主机地址(如:127.0.0.1 或 example.com
  3. 输入要扫描的端口:
    • 单个端口:如 80
    • 多个端口:如 80,443,8080
    • 端口范围:如 80-100
  4. 设置超时时间(100-5000毫秒)
  5. 选择扫描协议
  6. 点击「开始扫描」按钮
  7. 查看扫描结果
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>
相关推荐
idealzouhu2 小时前
【Android】深入浅出 JNI
android·开发语言·python·jni
廋到被风吹走2 小时前
【Java】【Jdk】Jdk11->Jdk17
java·开发语言·jvm
nike0good2 小时前
Goodbye 2025 题解
开发语言·c++·算法
Sheep Shaun2 小时前
STL中的unordered_map和unordered_set:哈希表的快速通道
开发语言·数据结构·c++·散列表
jllllyuz2 小时前
基于帧差法与ViBe算法的MATLAB前景提取
开发语言·算法·matlab
DsirNg2 小时前
CategoryTree 性能优化完整演进史
开发语言·前端
2501_944446002 小时前
Flutter&OpenHarmony字体与排版设计
android·javascript·flutter
小安同学iter2 小时前
Vue3 进阶核心:高级响应式工具 + 特殊内置组件核心解析
前端·javascript·vue.js·vue3·api
Roc.Chang2 小时前
Vue 3 setup 语法糖 computed 的深度使用
前端·javascript·vue.js