Nodejs随机空闲TCP端口

61
我的项目需要在每次实例化我的类时设置一个新的端口。
在Node.js中,我如何找到一个空闲的TCP端口以设置我的新套接字服务器?或者检查我指定的端口是否已经被使用了。
7个回答

110
您可以通过指定端口为0来绑定一个随机的、自由分配的端口。这样,您就不会受到竞态条件(例如,在您有机会绑定到端口之前,检测到一个开放的端口和一些进程绑定到该端口)的影响。
然后,您可以通过调用server.address().port来获取分配的端口。
示例:
var net = require('net');

var srv = net.createServer(function(sock) {
  sock.end('Hello world\n');
});
srv.listen(0, function() {
  console.log('Listening on port ' + srv.address().port);
});

这是一个很好的答案,但在这种情况下,如果要测试端口是否可用,我需要使用try/catch和重试,对吧? - Marcos Bergamo
2
如果你想使用特定的端口,只需尝试监听该端口并检查错误事件。如果出现错误且 error.code === 'EADDRINUSE',则说明该端口已被占用。 - mscdex
2
同样适用于 http.createServer(...).listen() - junvar
我喜欢将端口号设置为环境变量,但如果未指定,则回退到随机端口:srv.listen(process.env.PORT || 0); - Besworks

26

对于 Express 应用程序:

const app = require('express')();

const server = app.listen(0, () => {
  console.log('Listening on port:', server.address().port);
});

16

我使用这种简洁的形式:

import net from "net"

async function getPortFree() {
    return new Promise( res => {
        const srv = net.createServer();
        srv.listen(0, () => {
            const port = srv.address().port
            srv.close((err) => res(port))
        });
    })
}

用途:

const PORT = await getPortFree()

6

端口查找库:

https://github.com/http-party/node-portfinder

我建议你使用portfinder库,它在一周内已经下载了超过1000万次。

默认情况下,portfinder库会从8000开始搜索,并扫描到达最大端口号(65535)。

const portfinder = require('portfinder');

portfinder.getPort((err, port) => {
    //
    // `port` is guaranteed to be a free port
    // in this scope.
    //
});

5

要找到已打开的TCP端口,您可以使用模块portastic

您可以像这样查找端口:

port = require('portastic');

options = {
    min : 8000,
    max : 8005
}

port.find(options, function(err, data){
    if(err)
        throw err;
    console.log(data);
});

1
似乎 portfinder (https://www.npmjs.com/package/portfinder) 是更好的模块来处理这种情况。它甚至已经包含了 TypeScript 类型! - derpedy-doo

1
我在构建本地应用程序时多次使用了@mscdex的答案-感谢。然而,最近我有一个用例,如果可能的话最好坚持使用给定的端口,但如果该端口被占用,则仍然回退到另一个端口。
原因是我想使用localStorage保存应用程序首选项,出于安全考虑,它受限于相同站点、相同端口。因此,我希望尽可能使用3000端口,但如果需要,可以回退到3001或3002等端口,直到找到一个可用的端口。如果正在使用的端口不是3000,则会回退到存储在用户硬盘上的应用程序首选项的副本。
我使用以下模式实现这一点:
const http = require('http');
const server = http.createServer(function(req,res){ ... })

const config = {
  port: 3000,
  launched: false,
};

serverListen(server, config.port);

function serverListen(server, port){
  server.on('error', e => {
    console.log(`port ${config.port} is taken`)
    config.port +=1;
    server.close();
    serverListen(server, config.port);
  }).listen(port, function() {
    if (config.launched){
      return;
    }
    console.log('Listening on port ' + server.address().port);
    launchBrowser();
    config.launched = true;
  });
}

launchBrowser() 是一个启动浏览器的函数,用于打开链接 'http://127.0.0.1:' + server.address().port

这个应用是一个简单的本地服务器加浏览器应用。浏览器界面为 GUI,服务器则在用户硬盘上进行文件修改等操作。


这很好,但在尝试了10、100等端口后可能会出现错误,而不是所有的 ~65k 端口。 - chrismarx

0

等待端口

对于那些试图以“同步”的方式进行操作,且下游代码依赖于由操作系统选择的端口(例如,在创建测试服务器并将端口传递给测试时),以下方法一直对我很有用:

export const sleepForPort = async (httpServer: Server, ms: number): Promise<number> => {
  return new Promise<number>((resolve, reject) => {
    httpServer.listen(0, async () => {
      try{
        let addr = (httpServer.address() as AddressInfo | null)
        while(! (addr && addr.port) ) {
          await sleep(ms);
          addr = httpServer.address() as AddressInfo | null
        }
        resolve(addr.port);
      }catch(e){
        reject(e);
      }
    });
  });
}

const sleep = (ms: number) => {
  return new Promise(resolve => setTimeout(resolve, ms));
}

如果在运行与实际服务器的集成测试时,这将允许我们等待端口号可用,并返回此端口号,以便我们的testClient可以在此端口上访问localhost!
export const setupTests = async () => {
  const app = createExpressApp(...);
  const httpServer = createServer(app);
  server.installSubscriptionHandlers(httpServer); // Testing graphql subscriptions
  const port = await sleepForPort(httpServer, 100);
  return {port}
}

describe("Http server test", () => {
  let port: number;
  beforeAll(async () => {
    {port} = await setupTests()
  })
  it("Hits the right port", () => {
    const res = await fetch(`http://localhost:${port}/testing`)
    expect(res).toBeDefined()
    expect(res.status).toEqual(200);
  }
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接