Nodejs每日一講之Nodejs的進程間通信
本文轉載自微信公眾號「編程雜技 」,作者theanarkh。轉載本文請聯系編程雜技 眾號。
之前提了一個問題:nodejs中如何實現兄弟進程間的通信,大家分別列舉了redis、ZooKeeper,MessageChannel,還有linux操作系統提供的共享內存等一系列的進程間通信方式。所以今天來分享一下到底如何實現nodejs的進程間通信。這里的討論只限于linux系統,本機的進程。情況分為兩種:父子進程,兄弟進程。
在nodejs中,實現進程間通信的方式其實只有一種,那就是unix域。linux系統提供了很多種進程間通信的方式,那么為什么nodejs選擇unix域的,因為unix域相比其他進程間通信方式,有一個獨特的優勢,那就是傳遞文件描述符。unix域的實現是基于c/s模式的,類似tcp,udp。首先需要啟動一個unix域服務器,然后各個unix客戶端就可以"連接"這個服務器進行通信。
而在nodejs中父子進程的通信,底層使用的是socketpair,socketpair底層是也是unix域,不過他不是基于c/s模式的,如下圖所示。
那么nodejs中,兄弟進程是如何通信的呢?最簡單的方式就是通過主進程
但是這里多了一次中轉,很明顯效率上面會存著一些問題(相對直達和多一次中轉,性能比較是很明顯的,但是沒具體測過。而且據說egg就是這么搞的,有了解的同學可以交流一下)。所以我們一般通過unix域實現兄弟進程的通信,但是我們需要做的事情就比較多了。我們看看unix域的類型。Unix域支持兩種數據模式
1 流式( SOCK_STREAM),類似tcp,數據為字節流,需要應用層處理粘包問題。
2 數據報模式( SOCK_DGRAM ),類似udp,不需要處理數據邊界。
但是不巧的是Nodejs使用的是流式模式,所以問題就變得復雜。這時候我們通過c/s模式雖然可以實現兄弟進程間的通信,但是我們拿到的數據可能是"亂的",這時候為什么呢?一般情況下,客戶端給服務器發送1個字節,然后服務器處理,如果是基于這種場景,那么數據就不會是亂的。因為每次就是一個需要處理的數據單位。但是如果客戶端給服務器發送1個字節,服務器還沒來得及處理,客戶端又發送了一個字節,那么這時候服務器再處理的時候,就會有問題。因為兩個字節混一起了。就好比在一個tcp連接上先后發送兩個http請求一樣,如果服務器沒有辦法判斷兩個請求的數據邊界,那么處理就會有問題。
我們寫一個測試的例子。
unix域服務器
const net = require('net');net.createServer((client) => { client.on('data', (data) => { console.log(data.toString('utf-8')) })}).listen('\\\\?\\pipe\\ipc')
unix域客戶端
const net = require('net');const socket = net.connect({path: '\\\\?\\pipe\\ipc'});setInterval(() => { socket.write('1'); socket.write('2');},1000)
我們看一下輸出
我們看到輸出可能是1然后2。也可能是12。在tcp協議中,這叫做粘包。那么我們如何解決這個問題呢?我們可以定義一個應用層協議。類似http協議一樣,有了協議我們就知道,如何去解析收到的數據。接著我們還需要實現這個協議的解析器和封包邏輯,做完這些,我們就可以實現兄弟進程的通信了。
具體可參考ipc庫的實現https://github.com/theanarkh/nodejs-ipc