使用Nodejs实现一个简单的hello/hi程序

        本文基于Nodejs14.0.0分析的是node中的net模块,但是因为时间有限,只能对其中的某几个方法展开叙述。

 

        nodejs是一个异步I/O框架,依赖于实现异步功能的框架libuv且运行于Chrome V8引擎之上,所以其中很大一部分代码是用C++编写的,为了实现能在JS端调用C++代码,Node实现了一对函数(宏)来实现C++代码的导入和导出:NODE_MODULE_CONTEXT_AWARE_INTERNAL和internalBinding。

  •  NODE_MODULE_CONTEXT_AWARE_INTERNAL是一个宏,其原型为NODE_MODULE_CONTEXT_AWARE_INTERNAL(modname, regfunc),其实现了将C++模块挂靠在一个全局链表上。modname为模块的名称,regfunc为注册模块时调用的方法。如:
    NODE_MODULE_CONTEXT_AWARE_INTERNAL(tcp_wrap, node::TCPWrap::Initialize)

    是将TCPWrap类中的某些函数导出,node::TCPWrap::Initialize函数定义了一些常量以及将函数暴露给JS端。

  •  internalBinding用于导入一个模块,参数为模块的名称。如:
    const {TCP, TCPConnectWrap, constants: TCPConstants} = internalBinding('tcp_wrap');

     

        Node中的net模块可用于实现一个简单的TCP服务器,它实现了两个主要的类:Server和Socket和一个函数createConnection来发送接收数据,下面是一个简单的hello/hi程序:

        服务器端:

 

let net = require('net');

let server = net.createServer((socket) => {
    socket.on('data', (data) => {
        console.log(`sent from client:${data.toString()}`);
        socket.write('hi!');
    });

    socket.on('end', () => {
        console.log('a client disconnected');
    });
});

server.listen(1234, () => {
    console.log('server started');
});

        就上面的代码,首先,调用createServer函数创建一个Server类的实例server,这个函数接收一个回调函数作为参数。这个回调函数是用户需要实现的逻辑参数是一个Socket类的实例。然后server调用listen函数开始监听,listen函数可接受许多可选参数,在这里只使用了端口号和回调函数两个。

        客户端:

let net = require('net')

let client = net.createConnection({port: 1234}, ()=>{
    console.log('connected to server');
    client.write('hello!');
});

client.on('data', (data)=>{
    console.log(`response from server:${data.toString()}`);
    client.end();
});

client.on('end', ()=>{
    console.log('disconnected from server');
});

        createConnection接收两个的参数,一个是建立连接所需要的信息,如端口号,IP地址,地址类型等。另一个是回调函数。createConnection函数产生了一个Socket类型的实例,在这个实例上用户监听事件和处理数据。

 

        接下来首先分析服务端的代码:

        首先,是新建一个Server类的对象,构造函数的部分代码如下:

  if (typeof options === 'function') {
    connectionListener = options;
    options = {};
    this.on('connection', connectionListener);
  } else if (options == null || typeof options === 'object') {
    options = { ...options };

    if (typeof connectionListener === 'function') {
      this.on('connection', connectionListener);
    }
  } else {
    throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
  }

        构造函数主要做的是初始化成员变量和监听一个事件:'connection',这个事件被触发后将执行用户传入的回调函数。

        接下来,转到Server.listen函数,在这个函数主要干了一件事:根据传入参数的不同来决定如何给listenIncluster函数传参,listenInCluster函数用于在cluster上执行监听。至于cluster是做什么的,参见Nodejs的cluster模块解读。另外在这个函数中也监听了一个事件:'listening',当该事件被触发后,将调用用户传入listen函数的回调函数。

        在listenInCluster函数中会调用Server._listen2函数(指向setupListenHandle函数),这个函数主要做了五件事:创建一个句柄(这个句柄是一个TCP实例,是从C++模块中导入的),为句柄添加onconnection函数(这个函数的作用下面会提到),调用句柄的listen函数(开始涉及C++了),调用emitListeningNT函数(触发了'listening'事件)。另外,调用句柄的bind或者bind6函数,它们和listen一样,都是C++模块中的函数。

        接着说上面的listen函数。开头说过,JS代码可以执行C++模块中的函数,listen正是tcp_wrap.cc文件中的TCPWRAP::Connect函数,函数的源码如下:

void TCPWrap::Listen(const FunctionCallbackInfo<Value>& args) {
  TCPWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap,
                          args.Holder(),
                          args.GetReturnValue().Set(UV_EBADF));
  Environment* env = wrap->env();
  int backlog;
  if (!args[0]->Int32Value(env->context()).To(&backlog)) return;
  int err = uv_listen(reinterpret_cast<uv_stream_t*>(&wrap->handle_),
                      backlog,
                      OnConnection);
  args.GetReturnValue().Set(err);
}

这段代码中主要做的事是调用了uv_listen函数,uv_listen函数是libuv提供的函数,它将从上层传来的句柄和一个回调函数加入到一个队列中,其中可能会调用系统提供的listen函数(我猜的),当有客户端请求连接时,将调用OnConnection回调函数,这个函数在connection_warp.cc中,其代码如下:

 1 template <typename WrapType, typename UVType>
 2 void ConnectionWrap<WrapType, UVType>::OnConnection(uv_stream_t* handle,
 3                                                     int status) {
 4   WrapType* wrap_data = static_cast<WrapType*>(handle->data);
 5   CHECK_NOT_NULL(wrap_data);
 6   CHECK_EQ(&wrap_data->handle_, reinterpret_cast<UVType*>(handle));
 7 
 8   Environment* env = wrap_data->env();
 9   HandleScope handle_scope(env->isolate());
10   Context::Scope context_scope(env->context());
11 
12   // We should not be getting this callback if someone has already called
13   // uv_close() on the handle.
14   CHECK_EQ(wrap_data->persistent().IsEmpty(), false);
15 
16   Local<Value> client_handle;
17 
18   if (status == 0) {
19     // Instantiate the client javascript object and handle.
20     Local<Object> client_obj;
21     if (!WrapType::Instantiate(env, wrap_data, WrapType::SOCKET)
22              .ToLocal(&client_obj))
23       return;
24 
25     // Unwrap the client javascript object.
26     WrapType* wrap;
27     ASSIGN_OR_RETURN_UNWRAP(&wrap, client_obj);
28     uv_stream_t* client = reinterpret_cast<uv_stream_t*>(&wrap->handle_);
29     // uv_accept can fail if the new connection has already been closed, in
30     // which case an EAGAIN (resource temporarily unavailable) will be
31     // returned.
32     if (uv_accept(handle, client))
33       return;
34 
35     // Successful accept. Call the onconnection callback in JavaScript land.
36     client_handle = client_obj;
37   } else {
38     client_handle = Undefined(env->isolate());
39   }
40 
41   Local<Value> argv[] = { Integer::New(env->isolate(), status), client_handle };
42   wrap_data->MakeCallback(env->onconnection_string(), arraysize(argv), argv);
43 }

        在32行,执行uv_accept函数接受一个请求,如果成功,将调用句柄的onconnection函数(上面说的那个函数),在这个函数中,触发了'connection'事件,并将一个新建的socket对象传给监听器的回调函数。至此,在服务端,一个建立连接的过程结束。

        一张简要描述这个过程的图:

 

        接下来,是客户端的代码,在客户端使用createConnection(connect函数的别名)函数创建一个Socket实例并执行Socket.connect函数。在这个函数中,监听了一个事件:'connect',其回调函数是用户的代码。接下来,调用了internalConnect函数,在internalConnect函数中主要做了三件事:端口绑定,为句柄设置oncomplete属性(成功连接后要做的是,主要是触发'connect'事件),执行TCPWrap.Connect函数。在TCPWrap.Connect函数中,将执行uv_tcp_connect函数进行连接并将函数AfterConnect(在connection_wrap.cc文件中)作为它的回调,连接成功后将执行AfterConnect函数,AfterConnect执行了handle.oncomplete指向的函数:

req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);

即js模块中的afterConnect函数,在该函数中,触发了'connect'事件,开始执行传入createConnection的回调函数,连接过程结束。

 

只能写到这了,因为后面的看不懂了。。。

posted @ 2019-12-11 22:10  hujisha  阅读(631)  评论(0)    收藏  举报