const net = require('net'); /** * 端口检测工具 */ class PortChecker { /** * 检查端口是否可用(检测0.0.0.0,确保能绑定到所有地址) * @param {number} port - 端口号 * @param {string} host - 主机地址,默认 0.0.0.0(所有地址) * @returns {Promise} true 表示端口可用,false 表示已被占用 */ static checkPort(port, host = '0.0.0.0') { return new Promise((resolve) => { // 先尝试连接,看是否有服务在监听 const testSocket = new net.Socket(); testSocket.setTimeout(200); testSocket.on('connect', () => { // 能连接上,说明端口被占用 console.log(`[PortChecker] Port ${port} is in use (connection successful)`); testSocket.destroy(); resolve(false); }); testSocket.on('timeout', () => { // 超时,再用绑定方式检测 testSocket.destroy(); this._checkPortByBinding(port, host, resolve); }); testSocket.on('error', (err) => { testSocket.destroy(); if (err.code === 'ECONNREFUSED') { // 连接被拒绝,说明没有服务监听,再用绑定方式确认 this._checkPortByBinding(port, host, resolve); } else { // 其他错误,认为端口可用 resolve(true); } }); testSocket.connect(port, '127.0.0.1'); }); } static _checkPortByBinding(port, host, resolve) { const server = net.createServer(); server.once('error', (err) => { if (err.code === 'EADDRINUSE') { console.log(`[PortChecker] Port ${port} is in use (EADDRINUSE)`); resolve(false); } else { console.log(`[PortChecker] Port ${port} check error: ${err.code}`); resolve(false); } }); server.once('listening', () => { server.close(); console.log(`[PortChecker] Port ${port} is available`); resolve(true); }); server.listen(port, host); } /** * 查找可用端口(从指定端口开始递增查找) * @param {number} startPort - 起始端口 * @param {number} maxAttempts - 最大尝试次数,默认100 * @param {string} host - 主机地址,默认 0.0.0.0 * @returns {Promise} 返回可用的端口号,如果都不可用则返回 -1 */ static async findAvailablePort(startPort, maxAttempts = 100, host = '0.0.0.0') { console.log(`[PortChecker] Searching for available port starting from ${startPort}...`); for (let i = 0; i < maxAttempts; i++) { const port = startPort + i; const isAvailable = await this.checkPort(port, host); if (isAvailable) { console.log(`[PortChecker] ✓ Found available port: ${port}`); return port; } else { console.log(`[PortChecker] ✗ Port ${port} is in use, trying ${port + 1}...`); } } console.error(`[PortChecker] ✗ No available port found from ${startPort} to ${startPort + maxAttempts - 1}`); return -1; } /** * 等待端口开始监听(用于检测服务是否启动成功) * @param {number} port - 端口号 * @param {number} timeout - 超时时间(毫秒),默认30秒 * @param {string} host - 主机地址 * @returns {Promise} true 表示端口已开始监听 */ static async waitForPort(port, timeout = 30000, host = '127.0.0.1') { const startTime = Date.now(); while (Date.now() - startTime < timeout) { const isListening = await this.isPortListening(port, host); if (isListening) { console.log(`[PortChecker] Port ${port} is now listening`); return true; } // 等待500ms后重试 await new Promise(resolve => setTimeout(resolve, 500)); } console.error(`[PortChecker] Timeout waiting for port ${port} to listen`); return false; } /** * 检查端口是否正在监听 * @param {number} port - 端口号 * @param {string} host - 主机地址 * @returns {Promise} */ static isPortListening(port, host = '127.0.0.1') { return new Promise((resolve) => { const socket = new net.Socket(); socket.setTimeout(1000); socket.once('connect', () => { socket.destroy(); resolve(true); }); socket.once('timeout', () => { socket.destroy(); resolve(false); }); socket.once('error', () => { socket.destroy(); resolve(false); }); socket.connect(port, host); }); } } module.exports = PortChecker;