日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
編寫自己的js運(yùn)行時(shí)第二篇

前言:第一版基于V8實(shí)現(xiàn)了一個(gè)樸素版的服務(wù)器,第二版支持了多進(jìn)程架構(gòu),并且支持了SO_REUSEPORT。本文介紹一下第二版的一些實(shí)現(xiàn),設(shè)計(jì)上還是比較隨意的,目前主要關(guān)注功能。

創(chuàng)新互聯(lián)公司專注于萬(wàn)全網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供萬(wàn)全營(yíng)銷型網(wǎng)站建設(shè),萬(wàn)全網(wǎng)站制作、萬(wàn)全網(wǎng)頁(yè)設(shè)計(jì)、萬(wàn)全網(wǎng)站官網(wǎng)定制、微信小程序服務(wù),打造萬(wàn)全網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供萬(wàn)全網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。

首先我們看看第二版怎么使用。

1 通過fork共享端口

 
 
 
  1. const TCPServer = TCP(); 
  2.  
  3. const tcpServer = new TCPServer('127.0.0.1', 8989); 
  4.  
  5. tcpServer.socket(); 
  6. tcpServer.setReusePort(1); 
  7. tcpServer.bind(); 
  8. tcpServer.listen(); 
  9.  
  10. for (let i = 0; i < 3; i++) { 
  11.  
  12.     // 等于0說(shuō)明是子進(jìn)程,進(jìn)入處理連接的邏輯,否則是主進(jìn)程,循環(huán)創(chuàng)建多個(gè)進(jìn)程 
  13.     if (Child_Process.fork() === 0) { 
  14.         while(1) { 
  15.             tcpServer.accept(); 
  16.         }  
  17.     } 
  18.  
  19.  
  20. // 主進(jìn)程創(chuàng)建完子進(jìn)程后自己進(jìn)入阻塞狀態(tài) 
  21.  
  22. Child_Process.wait(); 

通過fork共享端口版本的原理是主進(jìn)程首先創(chuàng)建一個(gè)socket并且綁定一個(gè)端口。然后通過fork的方式讓多個(gè)子進(jìn)程共享監(jiān)聽的端口。最后主進(jìn)程進(jìn)入阻塞模式。核心實(shí)現(xiàn)是fork,我們看看代碼。

 
 
 
  1. static Local ChildProcess(Isolate * isolate) { 
  2.   Local target = ObjectTemplate::New(isolate); 
  3.   Local forkName = String::NewFromUtf8(isolate, "fork", NewStringType::kNormal, strlen("fork")).ToLocalChecked(); 
  4.   Local waitName = String::NewFromUtf8(isolate, "wait", NewStringType::kNormal, strlen("wait")).ToLocalChecked(); 
  5.  
  6.   target->Set(forkName, FunctionTemplate::New(isolate, Child_Process::Fork)); 
  7.   target->Set(waitName, FunctionTemplate::New(isolate, Child_Process::Wait)); 
  8.   Local obj; 
  9.   bool ignore = target->NewInstance(isolate->GetCurrentContext()).ToLocal(&obj); 
  10.   return obj; 
  11.  
  12. 第二版加入了進(jìn)程模塊,上面的代碼定義了進(jìn)程模塊的功能。然后注入到全局變量,No.js目前的設(shè)計(jì)中,每個(gè)模塊是一個(gè)全局變量,和我們使用Object、Array一樣,不像Node.js的C++模塊是鏈成一條鏈表。

     
     
     
    1. // 模塊名稱 
    2. Local child_process_name = String::NewFromUtf8(isolate, "Child_Process",  strlen("Child_Process")).ToLocalChecked();// 注冊(cè)全局變量 
    3. global->Set(context, child_process_name, ChildProcess(isolate)); 

    這樣就完成了模塊的注入,在JS層就可以使用了。下面我們看看具體的實(shí)現(xiàn)。

     
     
     
    1. class Child_Process { 
    2.     public: 
    3.  
    4.         static void Fork(const FunctionCallbackInfo& info) { 
    5.             info.GetReturnValue().Set(Number::New(info.GetIsolate(), fork())); 
    6.         } 
    7.  
    8.         static void Wait(const FunctionCallbackInfo& info) { 
    9.             int status; 
    10.             wait(&status); 
    11.         } 
    12.  
    13. }; 

    實(shí)現(xiàn)很簡(jiǎn)單,只是對(duì)fork函數(shù)的封裝,重點(diǎn)在于對(duì)fork函數(shù)的理解, 執(zhí)行fork函數(shù)后會(huì)創(chuàng)建一個(gè)子進(jìn)程,子進(jìn)程的fork返回0,主進(jìn)程返回子進(jìn)程id,通過這個(gè)特性,我們可以寫一個(gè)if判斷處理下一步的邏輯。

    2 通過fork+execve+reuserport共享端口

    第二種模式是比較復(fù)雜且比較高性能的模式,之前的文章介紹過不同服務(wù)器架構(gòu)的實(shí)現(xiàn)和優(yōu)缺點(diǎn),第一種fork共享端口的模式中,會(huì)有驚群和負(fù)載不均衡的問題,有興趣可以參考之前的文章,就不多介紹。接下來(lái)看第二種模式的使用(下面代碼是execve-server.js)。

     
     
     
    1. const TCPServer = TCP(); 
    2.  
    3. const tcpServer = new TCPServer('127.0.0.1', 8989); 
    4.  
    5. tcpServer.socket(); 
    6. tcpServer.setReusePort(1); 
    7. tcpServer.bind(); 
    8. tcpServer.listen(); 
    9.  
    10. const isMaster = Child_Process.getEnv("isMaster") === ""; 
    11.  
    12. if (isMaster) { 
    13.  
    14.     for (let i = 0; i < 3; i++) { 
    15.         Child_Process.execve("./No", "execve-server.js");   
    16.     } 
    17.     Child_Process.wait(); 
    18.  
    19. } else { 
    20.  
    21.     while(1) { 
    22.         tcpServer.accept(); 
    23.     } 
    24.  

    我們知道多個(gè)進(jìn)程是不能綁定同一個(gè)端口的,第一種模式中通過fork繞過了這個(gè)限制,第二版面對(duì)并解決了這個(gè)問題。上面代碼的邏輯看起來(lái)也很簡(jiǎn)單,主進(jìn)程創(chuàng)建多個(gè)子進(jìn)程,并且在每個(gè)子進(jìn)程里執(zhí)行同一個(gè)文件execve-server.js。然后在execve-server.js中通過環(huán)境變量isMaster區(qū)分主子進(jìn)程進(jìn)行不同的處理,當(dāng)然也可以執(zhí)行新的文件。這里是為了提到isMaster這個(gè)環(huán)境變量。上面代碼中,重點(diǎn)是setReusePort和execve,下面我們具體看一下實(shí)現(xiàn)。

     
     
     
    1. static Local ChildProcess(Isolate * isolate) { 
    2.   Local target = ObjectTemplate::New(isolate); 
    3.   Local execveName = String::NewFromUtf8(isolate, "execve", NewStringType::kNormal, strlen("execve")).ToLocalChecked(); 
    4.   Local getEnvName = String::NewFromUtf8(isolate, "getEnv", NewStringType::kNormal, strlen("getEnv")).ToLocalChecked(); 
    5.  
    6.   target->Set(execveName, FunctionTemplate::New(isolate, Child_Process::Execve)); 
    7.   target->Set(getEnvName, FunctionTemplate::New(isolate, Child_Process::GetEnv)); 
    8.   Local obj; 
    9.   bool ignore = target->NewInstance(isolate->GetCurrentContext()).ToLocal(&obj); 
    10.   return obj; 
    11.  
    12. 同樣,先定義入口使得JS可以調(diào)用。另外給TCP模塊定義了一個(gè)新接口setReusePort。

       
       
       
      1. SetProtoMethod(isolate, TCPServer, "setReusePort", TCPServer::TCPServerSetUserPort); 

      接下來(lái)看底層的實(shí)現(xiàn),首先看TCPServerSetUserPort的實(shí)現(xiàn)。

       
       
       
      1. static void TCPServerSetUserPort(const FunctionCallbackInfo& info) { 
      2.     int on = info[0].As()->Value(); 
      3.     GetTCPServer(info.Holder())->Setsockopt(SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); 
      4.  
      5.  
      6.  
      7.  
      8. int Setsockopt(int level, int optionName, const void *optionValue, socklen_t option_len) { 
      9.  
      10.     return setsockopt(listerFd, level, optionName, optionValue, option_len); 
      11.  

      簡(jiǎn)單地對(duì)setsockopt的封裝,沒有太多需要講的。下面看環(huán)境變量和execve的邏輯。

       
       
       
      1. class Child_Process { 
      2.     public: 
      3.  
      4.         static void GetEnv(const FunctionCallbackInfo& info) { 
      5.             String::Utf8Value key(info.GetIsolate(), info[0]); 
      6.             char * value = getenv(*key); 
      7.             // Logger::log(value); 
      8.             Local str = String::NewFromUtf8(info.GetIsolate(), value, NewStringType::kNormal, strlen(value)).ToLocalChecked(); 
      9.             info.GetReturnValue().Set(str); 
      10.         } 
      11.  
      12.         static void Execve(const FunctionCallbackInfo& info) { 
      13.             int length = info.Length(); 
      14.             char** args = new char*[length + 1]; 
      15.             int i = 0; 
      16.             for (i = 0; i < length; i++) { 
      17.                 String::Utf8Value arg(info.GetIsolate(), info[i]); 
      18.                 args[i] = strdup(*arg); 
      19.             } 
      20.             args[i] = NULL; 
      21.             char *env[] = { "isMaster=0", NULL }; 
      22.             // int fd[2]; 
      23.             // socketpair(AF_UNIX, SOCK_STREAM, 0, fd); 
      24.  
      25.             int pid = fork(); 
      26.             if (pid == 0) { 
      27.                 // close(fd[0]); 
      28.                 execve(args[0], args, env); 
      29.                 // execve會(huì)加載可執(zhí)行文件,從新的入口開始執(zhí)行,執(zhí)行到這說(shuō)明execve出錯(cuò)了 
      30.                 write(1, strerror(errno), sizeof(strerror(errno))); 
      31.                 exit(-1); 
      32.             } 
      33.             // close(fd[1]); 
      34.             if (args) { 
      35.                 for (int i = 0; i < length && args[i]; i++) { 
      36.                     free(args[i]); 
      37.                 } 
      38.                 delete [] args; 
      39.             } 
      40.         } 
      41.  
      42. }; 

      目前只實(shí)現(xiàn)了獲取環(huán)境變量的邏輯,主要是對(duì)getenv的封裝。execve的代碼看起來(lái)很多,主要是參數(shù)的處理,我們只需要關(guān)注下面的代碼。

       
       
       
      1. int pid = fork(); 
      2.  // 子進(jìn)程重新加載新的可執(zhí)行文件 
      3.  if (pid == 0) { 
      4.      // close(fd[0]); 
      5.      execve(args[0], args, env); 
      6.      // execve會(huì)加載可執(zhí)行文件,從新的入口開始執(zhí)行,執(zhí)行到這說(shuō)明execve出錯(cuò)了 
      7.      write(1, strerror(errno), sizeof(strerror(errno))); 
      8.      exit(-1); 
      9.  } 

      首先通過fork創(chuàng)建一個(gè)子進(jìn)程,然后通過execve加載要執(zhí)行的代碼(這里是./No execve-server.js)。重點(diǎn)是execve函數(shù)會(huì)重新加載可執(zhí)行文件,然后從新的地址(可執(zhí)行文件中指定)開始執(zhí)行,所以我們看到execve后是不需要return的,因?yàn)橄旅娴拇a不會(huì)執(zhí)行了,除非execve執(zhí)行出錯(cuò)了,這里我們打印錯(cuò)誤信息然后退出進(jìn)程。第二種模式的好處就是我們可以隨意在多個(gè)js文件中綁定同一個(gè)端口而不會(huì)報(bào)錯(cuò),這得益于SO_REUSEPORT的特性。SO_REUSEPORT讓每個(gè)進(jìn)程對(duì)應(yīng)一個(gè)連接隊(duì)列,解決了驚群?jiǎn)栴},并且內(nèi)核負(fù)責(zé)連接分發(fā)的復(fù)雜均衡,不僅提高了性能,同時(shí)使得應(yīng)用程序變得簡(jiǎn)單。

      3 和Node.js相比

      Node.js的進(jìn)程是通過fork+execve實(shí)現(xiàn)的,Cluster模塊基于進(jìn)程模塊實(shí)現(xiàn)了多進(jìn)程架構(gòu),主要有兩種模式:輪詢和共享,輪詢就是主進(jìn)程接收連接分發(fā)給子進(jìn)程處理,子進(jìn)程不接收連接只負(fù)責(zé)處理業(yè)務(wù)邏輯。這種模式的好處是沒有驚群現(xiàn)象,但是主進(jìn)程的能力會(huì)成為服務(wù)器的瓶頸,共享模式和本文的第一種一樣,多個(gè)子進(jìn)程共享一個(gè)端口,但是實(shí)現(xiàn)不一樣,本文是主進(jìn)程創(chuàng)建socket通過fork子進(jìn)程共享,Node.js是主進(jìn)程創(chuàng)建socket通過文件描述符的方式傳遞給子進(jìn)程,不過殊途同歸,主要是讓多個(gè)子進(jìn)程共享監(jiān)聽socket。本文的第二種模式,目前Node.js還不支持,因?yàn)镾O_REUSEPORT是比較新的特性,但是對(duì)性能提升非常大。

      后記:以上就是第二版新增的功能,我們已經(jīng)具備了一個(gè)可以處理請(qǐng)求的多進(jìn)程架構(gòu)服務(wù)器,但是目前還是單進(jìn)程里串行處理請(qǐng)求的,我們還需要很多東西,文件、IPC、事件驅(qū)動(dòng)模塊、HTTP解析器等等,后續(xù)會(huì)考慮把最近寫的Node.js io_uring Addon合進(jìn)來(lái)。最近把頭文件和V8靜態(tài)庫(kù)都打包了,有興趣的同學(xué)可以自行編譯運(yùn)行https://github.com/theanarkh/No.js。


      本文題目:編寫自己的js運(yùn)行時(shí)第二篇
      本文路徑:http://www.dlmjj.cn/article/cddshih.html