如何找到一個可用埠?
很多場景中,我們確實需要找一個空閒埠,比如啟動一個子程序監聽指定埠,然後通過這個埠與之通訊
然後實現方式就有很多了:
VSCode的實現
比如VSCode ,就是逐個連線,如果某個埠連線失敗,並且錯誤不是ECONNREFUSED的話,那麼就說明這個埠可用。
function doFindFreePort(startPort: number, giveUpAfter: number, clb: (port: number) => void): void { if (giveUpAfter === 0) { return clb(0); } const client = new net.Socket(); // If we can connect to the port it means the port is already taken so we continue searching client.once('connect', () => { dispose(client); return doFindFreePort(startPort + 1, giveUpAfter - 1, clb); }); client.once('data', () => { // this listener is required since node.js 8.x }); client.once('error', (err: Error & { code?: string }) => { dispose(client); // If we receive any non ECONNREFUSED error, it means the port is used but we cannot connect if (err.code !== 'ECONNREFUSED') { return doFindFreePort(startPort + 1, giveUpAfter - 1, clb); } // Otherwise it means the port is free to use! return clb(startPort); }); client.connect(startPort, '127.0.0.1'); }
這種方式完全混淆了“埠可以被監聽”和“埠不能連線”這兩個語義,雖然現在Linux上述程式碼能夠正常工作,但是保不準哪天新新增一個類似ECONNREFUSED這樣的錯誤碼呢。
vscode-mono-debug的實現
然後繼續找,發現vscode-mono-debug的實現 :通過不指定埠的方式listen,讓作業系統分配埠:
public static int FindFreePort(int fallback) { TcpListener l = null; try { l = new TcpListener(IPAddress.Loopback, 0); l.Start(); return ((IPEndPoint)l.LocalEndpoint).Port; } catch (Exception) { // ignore } finally { l.Stop(); } return fallback; }
這種方式好了一點,但是比人呼叫這個函式是為了拿到埠去監聽,而這個函式並不會保證這個埠不被別人佔用,所以這個實現還是有一點問題。
最終解決方案
目前來看,作業系統核心(至少Linux)沒有提供分配並預留埠的機制,所以得到空閒埠(或者叫可用埠)是不現實的。
目前,正確的做法就是:把取埠這件事情交給使用埠的單位。
比如,開頭提到的“啟動一個子程序監聽指定埠,然後通過這個埠與之通訊”,那就應該子程序通過不指定埠listen的方式,讓作業系統分配埠;並將這個埠告訴父程序(比如通過stdout)。