最近发现get-port-please这个工具,它可以在端口被占用时自动获取一个可用的端口,扒了扒下源码
核心原理
检测端口是否可用
原理:使用node的net模块尝试使用端口启动服务,如果成功启动则说明端口是可用的
源码:
import { createServer } from "node:net";
export function _tryPort(
port: PortNumber,
host: HostAddress,
): Promise<PortNumber | false> {
return new Promise((resolve) => {
const server = createServer();
server.unref();
server.on("error", () => {
resolve(false);
});
server.listen({ port, host }, () => {
const { port } = server.address() as AddressInfo;
server.close(() => {
resolve(isSafePort(port) && port);
});
});
});
}
核心代码:
启动服务 -> 成功 -> 关闭服务,释放端口
import { createServer } from "node:net";
const server = createServer();
server.listen(port, () => {
// 走到这里说明端口是ok的
server.close()
})
就是这么简单,但源码中有几个细节
server.unref()
这个方法用于减少引用计数,避免服务器因为正在运行,而无法程序退出const { port } = server.address()
这里“多此一举”的通过解构再次获取port,实际上用于后面获取随机端口
获取随机端口
当net使用的端口为0时,表示使用随机可用端口
server.listen(0)
通过server.address()可以获取端口号
const { port } = server.address()
实现一个简版的get-port-please
功能描述:
- 可指定端口或端口组
- 指定端口被占用时随机获取一个可用端口
使用示例:
const port = await getPort()
const port = await getPort(3001)
const port = await getPort([3000, 3001, 3002])
代码实现:
import { createServer } from 'node:net'
export async function getPort(port: number | number[] = 3000): Promise<number> {
if (typeof port === 'number') {
port = [port]
}
// 追加随机端口,0表示系统随机产生一个可用的端口
port.push(0)
for (const p of port) {
const _port = await checkPort(p)
if (_port) {
return _port
} else {
console.log(`端口 ${p} 被占用,尝试下一个...`)
}
}
return Promise.reject('no port available')
}
export function checkPort(port: number) {
return new Promise<false | number>((resolve, reject) => {
// 启动个服务,使用指定端口
const server = createServer()
// 即使服务器正在监听,但它不会阻止程序退出
server.unref()
// 如果端口不可用,则失败
server.on('error', (e) => {
resolve(false)
})
// 测试端口
server.listen(port, () => {
const { port: _port } = server.address() as { port: number }
server.close((err) => {
err ? resolve(false) : resolve(_port)
})
})
})
}
附源码:链接