本文介绍一下如何拓展js。下面代码是main函数的主要代码。
{
// 拿到一个全局变量,这个就是我们在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函数
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 属性的定义
private:
// 服务器地址
char * _ip;
int _port;
// 监听的socketfd
int listerFd;
// 保存关联的对象
Global<Object> persistent_handle_;
Isolate * _isolate;
2 构造和析构函数
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++类。类似下面的代码
class Obj {}
function test() {
this[0] = new Obj{}}
test.prototype.say = () => { "hello"; }
test.prototype.go = () => { }
new test();
我们看到执行new test的时候,我们拿到的是test的实例,但是我们具体类是Obj,所以需要保存两个对象的关系,从而在后面的调用中,首先拿到test实例,然后再拿到Obj实例,最后调用对应的实例方法。
3 静态函数
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 真正的逻辑处理
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如下
const TCPServer = TCP();const tcpServer = new TCPServer('127.0.0.1', 8989);tcpServer.socket();
tcpServer.bind();
tcpServer.listen();while(1) {
tcpServer.accept();}
最后我们写个客户端请求服务,每隔一段时间就发个TCP连接。
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)。