自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

編寫自己的js運(yùn)行時(shí)第二篇

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

[[410500]]

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

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

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說明是子進(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<Object> ChildProcess(Isolate * isolate) { 
  2.   Local<ObjectTemplate> target = ObjectTemplate::New(isolate); 
  3.   Local<String> forkName = String::NewFromUtf8(isolate, "fork", NewStringType::kNormal, strlen("fork")).ToLocalChecked(); 
  4.   Local<String> 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<Object> obj; 
  9.   bool ignore = target->NewInstance(isolate->GetCurrentContext()).ToLocal(&obj); 
  10.   return obj; 
  11.  

第二版加入了進(jìn)程模塊,上面的代碼定義了進(jìn)程模塊的功能。然后注入到全局變量,No.js目前的設(shè)計(jì)中,每個(gè)模塊是一個(gè)全局變量,和我們使用Object、Array一樣,不像Node.js的C++模塊是鏈成一條鏈表。

  1. // 模塊名稱 
  2. Local<Value> child_process_name = String::NewFromUtf8(isolate, "Child_Process",  strlen("Child_Process")).ToLocalChecked();// 注冊全局變量 
  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<Value>& info) { 
  5.             info.GetReturnValue().Set(Number::New(info.GetIsolate(), fork())); 
  6.         } 
  7.  
  8.         static void Wait(const FunctionCallbackInfo<Value>& info) { 
  9.             int status; 
  10.             wait(&status); 
  11.         } 
  12.  
  13. }; 

實(shí)現(xiàn)很簡單,只是對fork函數(shù)的封裝,重點(diǎn)在于對fork函數(shù)的理解, 執(zhí)行fork函數(shù)后會創(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共享端口的模式中,會有驚群和負(fù)載不均衡的問題,有興趣可以參考之前的文章,就不多介紹。接下來看第二種模式的使用(下面代碼是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è)限制,第二版面對并解決了這個(gè)問題。上面代碼的邏輯看起來也很簡單,主進(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<Object> ChildProcess(Isolate * isolate) { 
  2.   Local<ObjectTemplate> target = ObjectTemplate::New(isolate); 
  3.   Local<String> execveName = String::NewFromUtf8(isolate, "execve", NewStringType::kNormal, strlen("execve")).ToLocalChecked(); 
  4.   Local<String> 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<Object> obj; 
  9.   bool ignore = target->NewInstance(isolate->GetCurrentContext()).ToLocal(&obj); 
  10.   return obj; 
  11.  

同樣,先定義入口使得JS可以調(diào)用。另外給TCP模塊定義了一個(gè)新接口setReusePort。

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

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

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

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

  1. class Child_Process { 
  2.     public
  3.  
  4.         static void GetEnv(const FunctionCallbackInfo<Value>& info) { 
  5.             String::Utf8Value key(info.GetIsolate(), info[0]); 
  6.             char * value = getenv(*key); 
  7.             // Logger::log(value); 
  8.             Local<String> str = String::NewFromUtf8(info.GetIsolate(), value, NewStringType::kNormal, strlen(value)).ToLocalChecked(); 
  9.             info.GetReturnValue().Set(str); 
  10.         } 
  11.  
  12.         static void Execve(const FunctionCallbackInfo<Value>& 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會加載可執(zhí)行文件,從新的入口開始執(zhí)行,執(zhí)行到這說明execve出錯了 
  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)境變量的邏輯,主要是對getenv的封裝。execve的代碼看起來很多,主要是參數(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會加載可執(zhí)行文件,從新的入口開始執(zhí)行,執(zhí)行到這說明execve出錯了 
  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ù)會重新加載可執(zhí)行文件,然后從新的地址(可執(zhí)行文件中指定)開始執(zhí)行,所以我們看到execve后是不需要return的,因?yàn)橄旅娴拇a不會執(zhí)行了,除非execve執(zhí)行出錯了,這里我們打印錯誤信息然后退出進(jìn)程。第二種模式的好處就是我們可以隨意在多個(gè)js文件中綁定同一個(gè)端口而不會報(bào)錯,這得益于SO_REUSEPORT的特性。SO_REUSEPORT讓每個(gè)進(jìn)程對應(yīng)一個(gè)連接隊(duì)列,解決了驚群問題,并且內(nèi)核負(fù)責(zé)連接分發(fā)的復(fù)雜均衡,不僅提高了性能,同時(shí)使得應(yīng)用程序變得簡單。

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)程的能力會成為服務(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是比較新的特性,但是對性能提升非常大。

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

 

責(zé)任編輯:姜華 來源: 編程雜技
相關(guān)推薦

2024-03-21 09:15:58

JS運(yùn)行的JavaScrip

2021-08-27 00:21:19

JSJust源碼

2014-03-28 13:30:36

2022-10-08 00:00:00

V8channel對象

2011-06-21 10:28:49

Oracle

2015-07-20 15:44:46

Swift框架MJExtension反射

2022-08-02 10:26:09

網(wǎng)絡(luò)層網(wǎng)絡(luò)網(wǎng)絡(luò)協(xié)議

2017-04-10 14:46:29

AndroidGradleBuild.gradl

2011-03-14 16:05:17

2023-09-12 17:38:41

2022-10-08 00:06:00

JS運(yùn)行V8

2020-12-07 13:31:43

GoMutex開發(fā)者

2019-07-12 09:30:12

DashboardDockerDNS

2021-09-11 15:38:23

容器運(yùn)行鏡像開放

2021-10-14 09:53:38

鴻蒙HarmonyOS應(yīng)用

2021-09-07 11:19:42

操作系統(tǒng)華為鴻蒙

2022-09-07 08:11:30

LinuxLKRG結(jié)構(gòu)體

2024-01-29 08:07:42

FlinkYARN架構(gòu)

2023-08-27 21:07:02

2023-08-21 09:37:57

MySQL工具MariaDB
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號