155 lines
4.6 KiB
JavaScript
155 lines
4.6 KiB
JavaScript
|
|
const net = require('net');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 端口检测工具
|
|||
|
|
*/
|
|||
|
|
class PortChecker {
|
|||
|
|
/**
|
|||
|
|
* 检查端口是否可用(检测0.0.0.0,确保能绑定到所有地址)
|
|||
|
|
* @param {number} port - 端口号
|
|||
|
|
* @param {string} host - 主机地址,默认 0.0.0.0(所有地址)
|
|||
|
|
* @returns {Promise<boolean>} 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<number>} 返回可用的端口号,如果都不可用则返回 -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<boolean>} 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<boolean>}
|
|||
|
|
*/
|
|||
|
|
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;
|
|||
|
|
|