nodejs原理&;源碼賞析(5)】net模塊與通訊的實(shí)現(xiàn)

      網(wǎng)友投稿 662 2022-05-30

      示例代碼托管在:http://www.github.com/dashnowords/blogs

      博客園地址:《大史住在大前端》原創(chuàng)博文目錄

      華為云社區(qū)地址:【你要的前端打怪升級(jí)指南】

      【nodejs原理&源碼賞析(5)】net模塊與通訊的實(shí)現(xiàn)一. net模塊簡(jiǎn)介二. Client-Server的通訊2.1 server的建立2.2 Socket的建立三. IPC通訊四. 擼一個(gè)簡(jiǎn)易的cluster通訊模型

      一. net模塊簡(jiǎn)介

      net模塊是nodejs通訊功能實(shí)現(xiàn)的基礎(chǔ),nodejs中最常用的功能就是作為WebServer使用,建立服務(wù)器時(shí)使用的http.createServer就是在net.createServer方法的基礎(chǔ)上建立的。前端最熟悉的http協(xié)議屬于應(yīng)用層協(xié)議,應(yīng)用層的內(nèi)容想要發(fā)送出去,還需要將消息逐層下發(fā),通過(guò)傳輸層(tcp,udp),網(wǎng)際層(ip)和更底層的網(wǎng)絡(luò)接口后才能被傳輸出去。net模塊就是對(duì)分層通訊模型的實(shí)現(xiàn)。

      net模塊中有兩大主要抽象概念——net.Server和net.Socket。《deep-into-node》一書中對(duì)Socket概念進(jìn)行了解釋:

      Socket 是對(duì) TCP/IP 協(xié)議族的一種封裝,是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層。它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對(duì)用戶來(lái)說(shuō),一組簡(jiǎn)單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。

      Socket 還可以認(rèn)為是一種網(wǎng)絡(luò)間不同計(jì)算機(jī)上的進(jìn)程通信的一種方法,利用三元組(ip地址,協(xié)議,端口)就可以唯一標(biāo)識(shí)網(wǎng)絡(luò)中的進(jìn)程,網(wǎng)絡(luò)中的進(jìn)程通信可以利用這個(gè)標(biāo)志與其它進(jìn)程進(jìn)行交互。

      簡(jiǎn)單地說(shuō),net.Server實(shí)例可以監(jiān)聽一個(gè)端口(用于實(shí)現(xiàn)客戶端TCP連接通訊)或者地址(用于實(shí)現(xiàn)IPC跨進(jìn)程通訊),net.Socket實(shí)例可以建立一個(gè)套接字實(shí)例,它可以用來(lái)和server建立連接,連接建立后,就可以實(shí)現(xiàn)通訊了。你可以將socket想象成手機(jī),把server想象成基站,雖然不是很貼切,但可以降低理解難度。net相關(guān)API可以直接查看中文文檔【net模塊文檔】。

      二. Client-Server的通訊

      2.1 server的建立

      Server類的定義非常精簡(jiǎn),也很容易看懂:

      可以看到構(gòu)造函數(shù)基本上只是初始化了一些屬性,然后添加了對(duì)connection事件的響應(yīng)。服務(wù)器是net.Server類的實(shí)例,通過(guò)net.createServer([options][,onConnection] )方法建立,如果傳入一個(gè)函數(shù),則這個(gè)函數(shù)會(huì)作為connection事件的回調(diào)函數(shù),當(dāng)一個(gè)socket實(shí)例連接到server時(shí),connection事件就會(huì)觸發(fā),回調(diào)函數(shù)中的形參就指向了發(fā)起連接的socket實(shí)例。server實(shí)例并不能獨(dú)立工作,作為網(wǎng)絡(luò)服務(wù)器使用時(shí)需要需要調(diào)用listen方法來(lái)監(jiān)聽一個(gè)地址,示例如下:

      【nodejs原理&源碼賞析(5)】net模塊與通訊的實(shí)現(xiàn)

      const net = require('net');

      const { StringDecoder } = require('string_decoder');

      let decoder = new StringDecoder('utf8');

      let server = net.createServer(socket=>{

      console.log('接收連接');

      socket.on('data',data=>{

      console.log('收到來(lái)自客戶端的消息:',decoder.write(data));

      });

      socket.on('end',function(){

      console.log('socket從客戶端被關(guān)閉了');

      });

      });

      server.listen(12315);

      socket上以流的形式發(fā)送數(shù)據(jù),所以需要調(diào)用string_decoder模塊進(jìn)行解碼才能夠看到內(nèi)容,否則看到的就是原始的字節(jié)信息。上面的實(shí)例監(jiān)聽了12315端口。

      2.2 Socket的建立

      前文已經(jīng)提及Socket是對(duì)TCP/IP協(xié)議族的一種封裝。客戶端通訊套接字是net.Socket的實(shí)例,通過(guò)調(diào)用實(shí)例方法socket.connect(args)來(lái)和服務(wù)器建立連接,作為客戶端通訊套接字時(shí)需要監(jiān)聽端口號(hào),建立連接后,客戶端server通過(guò)connection事件的回調(diào)函數(shù)就可以拿到發(fā)起連接的socket實(shí)例,這樣客戶端和服務(wù)器就可以通訊了,其中一方通過(guò)socket.write()方法寫入數(shù)據(jù),另一方注冊(cè)的-socket.on('data',onData)回調(diào)函數(shù)就會(huì)收到信息。socket實(shí)例化示例如下:

      const net = require('net');

      let socket = new net.Socket();

      socket.connect(12315);

      //連接服務(wù)器

      socket.on('connect',c=>{

      console.log('成功建立和12315的連接')

      setTimeout(()=>{

      console.log('建立連接1s后發(fā)送消息');

      socket.write('SN:1231512315','utf8',function(){

      console.log('消息已發(fā)送');

      });

      },1000);

      });

      socket.on('data',function(resp){

      console.log('收到服務(wù)器返回消息:',resp);

      });

      socket.on('end',function(){

      console.log('socket從客戶端被關(guān)閉了');

      })

      客戶端connect連接服務(wù)器的動(dòng)作,就好比打電話前要先撥號(hào)一樣,等接通以后,你說(shuō)的話(也就是socket.write( )寫入的data)才能被發(fā)送過(guò)去。【代碼倉(cāng)的示例DEMO】中提供了相對(duì)完整的示例,分別放在server.js和client.js中,你可以通過(guò)控制臺(tái)打印的信息來(lái)觀察每條語(yǔ)句執(zhí)行的先后順序,熟悉從通信建立到消息收到再到服務(wù)器關(guān)閉的整個(gè)過(guò)程,記得要先起服務(wù)器,后起客戶端。

      Tips:你可以使用postman向這個(gè)server發(fā)一個(gè)GET請(qǐng)求,看看是什么樣子,對(duì)理解http和tcp/ip的關(guān)系有很大幫助,它非常直觀,反正我是第一次見(jiàn)。

      三. IPC通訊

      IPC通訊是指Inter Process Communication,也就是跨進(jìn)程通訊,上一節(jié)在提到cluster時(shí)已經(jīng)介紹過(guò)進(jìn)程之間是資源隔離的,所以跨進(jìn)程通訊也需要通過(guò)net模塊來(lái)建立消息管道。它的用法比較簡(jiǎn)單,只需要將server.listen( )和socket.connect( )的參數(shù)從端口號(hào)換成地址字符串就可以了。示例代碼如下:

      const net = require('net');

      const cluster = require('cluster');

      const path = require('path');

      const { StringDecoder } = require('string_decoder');

      let serverForIPC;//作為子進(jìn)程的server

      if (cluster.isMaster) {

      //主進(jìn)程執(zhí)行邏輯

      setupMaster();

      cluster.fork();//生成子進(jìn)程

      cluster.fork();//生成另一個(gè)子進(jìn)程

      } else {

      //子進(jìn)程執(zhí)行邏輯

      setupWorker();

      }

      //主進(jìn)程邏輯

      function setupMaster() {

      //作為Server監(jiān)聽子進(jìn)程消息

      let decoder = new StringDecoder('utf8');

      //windows系統(tǒng)中要求的IPC通訊命名規(guī)則

      let ipcPath = path.join('\\\\?\\pipe', process.cwd(), 'dashipc');

      serverForIPC = net.createServer(socket=>{

      console.log(`[master]:子進(jìn)程通過(guò)ipcServer連接到主進(jìn)程`);

      socket.on('data',data=>{

      console.log('[master]:收到來(lái)自子進(jìn)程的消息:',decoder.write(data));

      });

      });

      //IPC-server端監(jiān)聽指定地址

      serverForIPC.listen(ipcPath);

      }

      //子進(jìn)程邏輯

      function setupWorker() {

      let ipcPath = path.join('\\\\?\\pipe', process.cwd(), 'dashipc');

      let socket = new net.Socket();

      //子進(jìn)程的socket連接主進(jìn)程中監(jiān)聽的地址

      socket.connect(ipcPath,c=>{

      console.log(`[child-${process.pid}]:pid為${process.pid}的子進(jìn)程已經(jīng)連接到主進(jìn)程`);

      //過(guò)一秒后發(fā)個(gè)消息測(cè)試一下

      setTimeout(()=>{

      socket.write(`${process.pid}的消息:SN1231512315`,'utf8',function(){

      console.log(`[child-${process.pid}]:消息已發(fā)送`);

      });

      },1000);

      });

      }

      需要注意盡管主進(jìn)程和子進(jìn)程運(yùn)行的是同樣的腳本,但執(zhí)行的具體邏輯由cluster.isMaster進(jìn)行了區(qū)分。當(dāng)主進(jìn)程的腳本運(yùn)行時(shí)會(huì)建立一個(gè)IPC通訊管道的server端并監(jiān)聽指定地址,然后通過(guò)cluster.fork生成子進(jìn)程,子進(jìn)程會(huì)執(zhí)行setupWorker( )方法的邏輯,新建一個(gè)socket實(shí)例并連接主進(jìn)程監(jiān)聽的地址,這樣跨進(jìn)程通訊就建立了。示例代碼放置在代碼倉(cāng)中的ipc.js中,運(yùn)行結(jié)果如下:

      四. 擼一個(gè)簡(jiǎn)易的cluster通訊模型

      既然客戶端通訊和跨進(jìn)程通訊都實(shí)現(xiàn)了,那么把它們連起來(lái)協(xié)調(diào)好,其實(shí)就可以復(fù)現(xiàn)cluster集群模塊的功能了,雖然它不能等同于cluster的源碼,cluster中跨進(jìn)程通訊是直接可以使用的,不需要自己手動(dòng)建立,但“造輪子”對(duì)于理解集群通訊機(jī)制非常有幫助。簡(jiǎn)易模型的基本方案如下,邏輯的順序已經(jīng)標(biāo)記出來(lái)了,在前文的基礎(chǔ)上實(shí)際上增加的只是調(diào)度相關(guān)的功能(也就是橙色背景的部分):

      首先主線程和子線程之間建立IPC通訊,連接建立后,由子進(jìn)程將自己的pid通過(guò)socket發(fā)給主進(jìn)程,這樣主進(jìn)程就知道連接到IPCserver的socket是哪個(gè)子進(jìn)程連過(guò)來(lái)的了,demo在內(nèi)部構(gòu)建了一個(gè)type屬性為internal_init的消息來(lái)完成這個(gè)登記動(dòng)作,然后啟動(dòng)一個(gè)接收客戶端連接的Server,監(jiān)聽指定的端口。接下來(lái)到了第6步,客戶端新建了socket連接到了主線程Client Server監(jiān)聽的端口,clientServer把它發(fā)過(guò)來(lái)的socket傳給調(diào)度中心,調(diào)度中心根據(jù)一定規(guī)則(demo中直接就簡(jiǎn)單粗暴地輪換使用各個(gè)線程)決定將這個(gè)socket與哪個(gè)worker socket相匹配(所謂匹配就是指client socket發(fā)來(lái)的消息應(yīng)該調(diào)用哪個(gè)worker socket的write方法來(lái)分發(fā)給對(duì)應(yīng)的子進(jìn)程),然后將這個(gè)客戶端socket登記到匹配記錄表中某條記錄的client socket上,這樣通訊通道就建立好了。

      當(dāng)客戶端調(diào)用socket.write來(lái)寫入數(shù)據(jù)時(shí),主線程就會(huì)收到這個(gè)數(shù)據(jù),然后根據(jù)已經(jīng)建立好的socket關(guān)系把這條消息write到子進(jìn)程,子進(jìn)程處理完后在消息體中增加一個(gè)pid屬性標(biāo)明這個(gè)消息是哪個(gè)進(jìn)程處理的(這個(gè)標(biāo)記也可以在主進(jìn)程中添加,因?yàn)橹鬟M(jìn)程中維護(hù)的有pid,client socket和worker socket的對(duì)應(yīng)關(guān)系),然后調(diào)用socket.write發(fā)回給主進(jìn)程,主進(jìn)程根據(jù)消息的pid屬性在記錄表中找到這個(gè)消息應(yīng)該由哪個(gè)client socket來(lái)返回,找到后調(diào)用它的end方法將數(shù)據(jù)返回給客戶端,這樣就完成了一次請(qǐng)求分發(fā)。

      demo中提供了示例,ipc_http.js是簡(jiǎn)易集群模型的服務(wù)端,ipc_http_client.js是客戶端,前后一共發(fā)送了3次請(qǐng)求,結(jié)果如下:

      服務(wù)端的日志:

      客戶端的請(qǐng)求:

      上面的示例僅僅是為了幫助理解網(wǎng)絡(luò)通信和跨進(jìn)程通信協(xié)作的原理,并不代表cluster的源碼,但通信層面的原理是類似的,實(shí)際開發(fā)中跨進(jìn)程通訊時(shí)不需要自己再構(gòu)建IPC消息通道,因?yàn)樽舆M(jìn)程返回的process上就已經(jīng)集成了跨進(jìn)程通訊能力,理解這個(gè)簡(jiǎn)化的模型對(duì)閱讀cluster模塊的通訊原理能夠提供很好的過(guò)渡。

      附件: net_demo.rar 4KB 下載次數(shù):1次

      Node.js

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:Android WebView常見(jiàn)問(wèn)題
      下一篇:上映 10 天,票房就突破 10 億的《海王》真的有那么好看?
      相關(guān)文章
      亚洲免费一级视频| 人人狠狠综合久久亚洲88| 图图资源网亚洲综合网站| 国产偷国产偷亚洲高清日韩| 国产99久久亚洲综合精品| 亚洲国产精品精华液| 亚洲乱妇熟女爽到高潮的片| 亚洲另类无码专区首页| 亚洲精品动漫免费二区| 亚洲av永久中文无码精品| 亚洲国产一区二区三区在线观看| 亚洲综合在线一区二区三区 | 亚洲色欲久久久综合网| 国产亚洲精久久久久久无码AV| 亚洲国产天堂久久久久久| 亚洲日本va午夜中文字幕久久| 亚洲色偷偷狠狠综合网| 亚洲视频在线一区二区| 国产亚洲精品看片在线观看| 久久久久国产亚洲AV麻豆| 亚洲情综合五月天| 亚洲AV无码国产精品麻豆天美| 亚洲不卡中文字幕无码| 亚洲五月六月丁香激情| 亚洲成A∨人片在线观看无码| 亚洲中字慕日产2020| 在线观看亚洲AV日韩A∨| 亚洲成a人片在线观看天堂无码| 苍井空亚洲精品AA片在线播放| 亚洲第一成人影院| 怡红院亚洲怡红院首页| 亚洲精品夜夜夜妓女网| 亚洲国产成人私人影院| 亚洲国产精品久久久久秋霞影院 | 国产成人精品久久亚洲高清不卡 国产成人精品久久亚洲 | 亚洲av综合avav中文| 久久精品亚洲中文字幕无码麻豆| 亚洲综合激情六月婷婷在线观看| 亚洲丝袜中文字幕| 在线观看亚洲免费视频| 在线亚洲精品自拍|