使用Node获取一个可用端口——get-port-please源码一窥

613 阅读2分钟

最近发现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()
})

就是这么简单,但源码中有几个细节

  1. server.unref()
    这个方法用于减少引用计数,避免服务器因为正在运行,而无法程序退出
  2. const { port } = server.address()
    这里“多此一举”的通过解构再次获取port,实际上用于后面获取随机端口

获取随机端口

net使用的端口为0时,表示使用随机可用端口

server.listen(0)

通过server.address()可以获取端口号

const { port } = server.address()

实现一个简版的get-port-please

功能描述:

  1. 可指定端口或端口组
  2. 指定端口被占用时随机获取一个可用端口

使用示例:

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)
            })
        })
    })
}

附源码:链接