前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >编写自己的js运行时第一篇

编写自己的js运行时第一篇

作者头像
theanarkh
发布2021-07-08 16:04:50
发布2021-07-08 16:04:50
54500
代码可运行
举报
文章被收录于专栏:原创分享原创分享
运行总次数:0
代码可运行

本文介绍一下如何拓展js。下面代码是main函数的主要代码。

代码语言:javascript
代码运行次数:0
复制
{
      // 拿到一个全局变量,这个就是我们在js里对应的全局变量
      Local<Object> global =  context->Global();
      Local<Value> key = String::NewFromUtf8(isolate, "TCP",
                          NewStringType::kNormal,
                          strlen("TCP")).ToLocalChecked();
      Local<Value> cbdata = String::NewFromUtf8(isolate, "dummy",
                          NewStringType::kNormal,
                          strlen("dummy")).ToLocalChecked();
      // 定义一个函数                   
      Local<Function> func =
      Function::New(context,
                        // 执行func时会调用Invoke,cbdata是入参
                        Invoke,
                        cbdata).ToLocalChecked();   
      // 把函数注册到全局变量,这样我们在js里就可以使用key该函数了                                    
      Maybe<bool> ignore = global->Set(context, key, func);
      // 打开文件
      int fd = open(argv[1], O_RDONLY);
      struct stat info;
      // 取得文件信息
      fstat(fd, &info);
      // 分配内存保存文件内容
      char *ptr = (char *)malloc(info.st_size + 1);
      // 读取文件搭配ptr,Mac os的read函数定义第二个参数是void *
      read(fd, (void *)ptr, info.st_size);
      // 要执行的js代码
      Local<String> source = String::NewFromUtf8(isolate, ptr,
                          NewStringType::kNormal,
                          info.st_size).ToLocalChecked();

      // 编译
      Local<Script> script =
          Script::Compile(context, source).ToLocalChecked();
      // 解析完应该没用了,释放内存
      free(ptr);
      // 执行
      Local<Value> result = script->Run(context).ToLocalChecked();
    }

上面的代码主要定义里一个全局变量。然后执行一段js代码,当我们执行TCP这个函数时,v8就会调用Invoke函数

代码语言:javascript
代码运行次数:0
复制
static void SetProtoMethod(Isolate * isolate, v8::Local<v8::FunctionTemplate> functionTemplate, const char * name, FunctionCallback callback) {
  // 以callback为函数,新建一个函数模板
  Local<FunctionTemplate> t = FunctionTemplate::New(isolate, callback);
  const NewStringType type = NewStringType::kNormal;
  Local<String> name_string = String::NewFromUtf8(isolate, name, type).ToLocalChecked();
  // 拿到functionTemplate的原型对象,设置属性
  functionTemplate->PrototypeTemplate()->Set(name_string, t);
  // 设置函数的名称
  t->SetClassName(name_string);}static void Invoke(const FunctionCallbackInfo<Value>& info) {
  Isolate * isolate = info.GetIsolate();
  // 新建一个函数模版,模版函数是TCPServer::NewTCPServer
  Local<FunctionTemplate> TCPServer = FunctionTemplate::New(isolate, TCPServer::NewTCPServer);
  Local<String> tcpServerString = String::NewFromUtf8(isolate, "TCPServer",
                          NewStringType::kNormal,
                          strlen("TCPServer")).ToLocalChecked();
  // 函数名
  TCPServer->SetClassName(tcpServerString);
  // 预留一个指针空间
  TCPServer->InstanceTemplate()->SetInternalFieldCount(1);

  // 设置TCPServer的原型方法
  SetProtoMethod(isolate, TCPServer, "socket", TCPServer::TCPServerSocket);
  SetProtoMethod(isolate, TCPServer, "bind", TCPServer::TCPServerBind);
  SetProtoMethod(isolate, TCPServer, "listen", TCPServer::TCPServerListen);
  SetProtoMethod(isolate, TCPServer, "accept", TCPServer::TCPServerAccept);
  //SetProtoMethod(isolate, TCPServer, "setsockopt", TCPServer::TCPServerSetsockopt);
  info.GetReturnValue().Set(TCPServer->GetFunction(isolate->GetCurrentContext()).ToLocalChecked());}

Invoke函数主要是定义里一个函数并返回给js层,所以我们在js里执行TCP()后就拿到了一个函数。接着我们new这个函数。这时候会执行TCPServer::NewTCPServer并传入一个C++对象。下面我们来看TCPServer类的定义。

1 属性的定义

代码语言:javascript
代码运行次数:0
复制
private:
        // 服务器地址
        char * _ip;
        int _port;
        // 监听的socketfd
        int listerFd;
        // 保存关联的对象
        Global<Object> persistent_handle_;
        Isolate * _isolate;

2 构造和析构函数

代码语言:javascript
代码运行次数:0
复制
        static TCPServer * GetTCPServer(Local<Object> object) {
            return reinterpret_cast<TCPServer *>((*reinterpret_cast<v8::Local<Object>*>(&object))->GetAlignedPointerFromInternalField(0));
        }

        TCPServer(Isolate* isolate, Local<Object> object, char * ip, int port): _isolate(isolate),persistent_handle_(isolate, object), _ip(ip), _port(port) {
            object->SetAlignedPointerInInternalField(0, static_cast<void*>(this));
        }
        ~TCPServer() {
            Close();
            (*reinterpret_cast<v8::Local<Object>*>((&persistent_handle_)))->SetAlignedPointerInInternalField(0, nullptr);
        }

这两个函数主要的作用是关联和解除和object的关系。为什么需要这样做呢?因为在js层调用c++层定义的函数时,上下文对象是一个由函数模版创建出来的一般对象,而不是我们自己定义的C++类。类似下面的代码

代码语言:javascript
代码运行次数:0
复制
class Obj {}
function test() {
    this[0] = new Obj{}}
test.prototype.say = () => { "hello"; }
test.prototype.go = () => { }
new test();

我们看到执行new test的时候,我们拿到的是test的实例,但是我们具体类是Obj,所以需要保存两个对象的关系,从而在后面的调用中,首先拿到test实例,然后再拿到Obj实例,最后调用对应的实例方法。

3 静态函数

代码语言:javascript
代码运行次数:0
复制
static TCPServer * GetTCPServer(Local<Object> object) {
     return reinterpret_cast<TCPServer *>((*reinterpret_cast<v8::Local<Object>*>(&object))->GetAlignedPointerFromInternalField(0));}static void NewTCPServer(const FunctionCallbackInfo<Value>& info) {
   String::Utf8Value ip_address(info.GetIsolate(), info[0]);
   int port = info[1].As<Uint32>()->Value();
   new TCPServer(info.GetIsolate(),info.This(), *ip_address, port);}static void TCPServerSocket(const FunctionCallbackInfo<Value>& info) {
   GetTCPServer(info.Holder())->Socket();}static void TCPServerBind(const FunctionCallbackInfo<Value>& info) {
   GetTCPServer(info.Holder())->Bind();}static void TCPServerListen(const FunctionCallbackInfo<Value>& info) {
   GetTCPServer(info.Holder())->Listen();}static void TCPServerAccept(const FunctionCallbackInfo<Value>& info) {
   GetTCPServer(info.Holder())->Accept();}

静态函数的逻辑非常简单,就是从调用的上下文中获取真正的类实例,然后调用对应的方法。

4 真正的逻辑处理

代码语言:javascript
代码运行次数:0
复制
        int Socket() {
            listerFd = socket(AF_INET, SOCK_STREAM, 0);
            return listerFd;
        }

        int Bind() {    
            struct sockaddr_in serv_addr;
            memset(&serv_addr, 0, sizeof(serv_addr));
            serv_addr.sin_family = AF_INET;
            serv_addr.sin_addr.s_addr = inet_addr(_ip);
            serv_addr.sin_port = htons(_port);
            return bind(listerFd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        }

        int Listen() {
            return listen(listerFd, 512);
        }

        int Accept() {
            int clientFd = accept(listerFd, nullptr, nullptr);
            const char * rsp = "connect ok";
            write(clientFd, rsp, sizeof(rsp));
            close(clientFd);
            return 0;
        }

        int Setsockopt(int level, int optionName, const void *optionValue, socklen_t option_len) {
            return setsockopt(listerFd, level, optionName, optionValue, option_len);
        }

        int Close() {
            return close(listerFd);
        }

真正的逻辑处理就是实现了一个简单的TCP服务器,然后阻塞在accept等待请求。

一切准备就绪,开始编译,编译后会生成一个可执行文件,命名为No。软链到系统搜索目录下(sudo ln -s xxx/No /usr/local/bin/No),这时候我们就可以执行执行No test.js启动一个服务器了。test.js如下

代码语言:javascript
代码运行次数:0
复制
const TCPServer = TCP();const tcpServer = new TCPServer('127.0.0.1', 8989);tcpServer.socket();
tcpServer.bind();
tcpServer.listen();while(1) {
    tcpServer.accept();}

最后我们写个客户端请求服务,每隔一段时间就发个TCP连接。

代码语言:javascript
代码运行次数:0
复制
const net = require('net');
function handle() {
    setTimeout(() => {
        const socket = net.connect({host: '127.0.0.1', port: 8989});
        socket.on('connect', () => {
            console.log('ok');
            socket.destroy();
            handle();
        });
    }, 1000);}handle();

后记:这只是个demo,主要是完成拓展js的能力,仓库点击(https://github.com/theanarkh/No.js)。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档