当前位置: 首页 > 科技观察

WritingYourOwnJSRuntimePart2

时间:2023-03-14 01:24:22 科技观察

前言:第一个版本基于V8实现了一个简单版本的服务器,第二个版本支持多进程架构,支持SO_REUSEPORT。本文介绍第二版的一些实现。设计上还是比较随意的,目前的重点是功能。首先,让我们看看如何使用第二个版本。1通过fork共享端口constTCPServer=TCP();consttcpServer=newTCPServer('127.0.0.1',8989);tcpServer.socket();tcpServer.setReusePort(1);tcpServer.bind();tcpServer.listen();for(leti=0;i<3;i++){//等于0表示是子进程,进入处理连接的逻辑,否则是主进程,循环创建多个进程if(Child_Process.fork()===0){while(1){tcpServer.accept();}}}//主进程创建子进程后进入阻塞状态Child_Process.wait();通过fork共享端口版本的原理是主进程先创建一个socket并绑定一个端口。然后通过fork让多个子进程共享监听端口。最后主进程进入阻塞模式。核心实现是fork,我们看代码。staticLocalChildProcess(Isolate*isolate){Localtarget=ObjectTemplate::New(isolate);LocalforkName=String::NewFromUtf8(isolate,"fork",NewStringType::kNormal,strlen("fork")).ToLocalChecked();LocalwaitName=String::NewFromUtf8(isolate,"wait",NewStringType::kNormal,strlen("wait")).ToLocalChecked();target->Set(forkName,FunctionTemplate::New(isolate,Child_Process::Fork));target->Set(waitName,FunctionTemplate::New(isolate,Child_Process::Wait));Localobj;boolignore=target->NewInstance(isolate->GetCurrentContext()).ToLocal(&obj);returnobj;}第二版增加了流程模块,上面的代码定义了流程模块的功能。然后注入到全局变量中。No.js目前的设计中,每个模块都是一个全局变量,就像我们使用Object和Array一样,不像Node.js的C++模块,是链接成一个链表。//模块名称Localchild_process_name=String::NewFromUtf8(isolate,"Child_Process",strlen("Child_Process")).ToLocalChecked();//注册全局变量global->Set(context,child_process_name,ChildProcess(isolate));这样就完成了模块的注入,可以在JS层使用了。下面看看具体的实现。classChild_Process{public:staticvoidFork(constFunctionCallbackInfo&info){info.GetReturnValue().Set(Number::New(info.GetIsolate(),fork()));}staticvoidWait(constFunctionCallbackInfo&info){intstatus;等待(&状态);}};实现很简单,就是对fork函数的封装,重点在于fork函数的理解,执行fork函数后,会创建一个子进程,子进程的fork返回0,以及主进程返回子进程id,通过这个特性,我们可以写一个if判断下一步处理的逻辑。2通过fork+execve+reuserport共享端口第二种模式更复杂,性能更高。上一篇文章介绍了不同服务器架构的实现和优缺点。在fork共享端口的第一种模式中,会有惊喜。关于分组和负载不均衡的问题,有兴趣的可以参考上一篇文章,就不多介绍了。接下来看第二种模式的使用(以下代码为execve-server.js)。constTCPServer=TCP();consttcpServer=newTCPServer('127.0.0.1',8989);tcpServer.socket();tcpServer.setReusePort(1);tcpServer.bind();tcpServer.listen();constisMaster=Child_Process.getEnv("isMaster")==="";if(isMaster){for(leti=0;i<3;i++){Child_Process.execve("./No","execve-server.js");}Child_Process。wait();}else{while(1){tcpServer.accept();}}我们知道,多个进程不能绑定同一个端口。在第一种模式下,这个限制被fork绕过,第二个版本面临并解决了这个问题。上面代码的逻辑看起来也很简单,主进程创建多个子进程,在每个子进程中执行同一个文件execve-server.js。然后在execve-server.js中通过环境变量isMaster来区分主子进程进行不同的处理,当然也可以执行新的文件。这里要提一下环境变量isMaster。上面代码中,重点是setReusePort和execve。让我们详细看一下实现。staticLocalChildProcess(Isolate*isolate){Localtarget=ObjectTemplate::New(isolate);LocalexecveName=String::NewFromUtf8(isolate,"execve",NewStringType::kNormal,strlen("execve")).ToLocalChecked();LocalgetEnvName=String::NewFromUtf8(isolate,"getEnv",NewStringType::kNormal,strlen("getEnv")).ToLocalChecked();target->Set(execveName,FunctionTemplate::New(isolate,Child_Process::Execve));target->Set(getEnvName,FunctionTemplate::New(isolate,Child_Process::GetEnv));Localobj;boolignore=target->NewInstance(isolate->GetCurrentContext()).ToLocal(&obj);returnobj;}同样先定义入口,这样JS才能调用。此外,还为TCP模块定义了一个新的接口setReusePort。SetProtoMethod(隔离,TCPServer,“setReusePort”,TCPServer::TCPServerSetUserPort);接下来看底层实现,先看TCPServerSetUserPort的实现。staticvoidTCPServerSetUserPort(constFunctionCallbackInfo&info){inton=info[0].As()->Value();GetTCPServer(info.Holder())->Setsockopt(SOL_SOCKET,SO_REUSEPORT,&on,sizeof(on));}intSetsockopt(intlevel,intoptionName,constvoid*optionValue,socklen_toption_len){returnssetsockopt(listerFd,level,optionName,optionValue,option_len);}简单封装了setsockopt,就不多说了。下面看看环境变量和execve的逻辑。classChild_Process{public:staticvoidGetEnv(constFunctionCallbackInfo&info){String::Utf8Valuekey(info.GetIsolate(),info[0]);char*value=getenv(*key);//Logger::log(value);Localstr=String::NewFromUtf8(info.GetIsolate(),value,NewStringType::kNormal,strlen(value)).ToLocalChecked();info.GetReturnValue().Set(str);}staticvoidExecve(constFunctionCallbackInfo)<值>&info){intlength=info.Length();char**args=newchar*[length+1];inti=0;for(i=0;i