侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

node.js的http.createServer过程深入解析

前端  /  管理员 发布于 5年前   219

下面是nodejs创建一个服务器的代码。接下来我们一起分析这个过程。

var http = require('http');http.createServer(function (request, response) {  response.end('Hello World');}).listen(9297);

首先我们去到lib/http.js模块看一下这个函数的代码。

function createServer(requestListener) { return new Server(requestListener);}

只是对_http_server.js做了些封装。我们继续往下看。

function Server(requestListener) { if (!(this instanceof Server)) return new Server(requestListener); net.Server.call(this, { allowHalfOpen: true }); // 收到http请求时执行的回调 if (requestListener) {  this.on('request', requestListener); } this.httpAllowHalfOpen = false; // 建立tcp连接的回调 this.on('connection', connectionListener); this.timeout = 2 * 60 * 1000; this.keepAliveTimeout = 5000; this._pendingResponseData = 0; this.maxHeadersCount = null;}util.inherits(Server, net.Server);

发现_http_server.js也没有太多逻辑,继续看lib/net.js下的代码。

function Server(options, connectionListener) { if (!(this instanceof Server))  return new Server(options, connectionListener); EventEmitter.call(this); // connectionListener在http.js处理过了 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 errors.TypeError('ERR_INVALID_ARG_TYPE',                'options',                'Object',                options); } this._connections = 0; ...... this[async_id_symbol] = -1; this._handle = null; this._usingWorkers = false; this._workers = []; this._unref = false; this.allowHalfOpen = options.allowHalfOpen || false; this.pauseOnConnect = !!options.pauseOnConnect;}

至此http.createServer就执行结束了,我们发现这个过程还没有涉及到很多逻辑,并且还是停留到js层面。接下来我们继续分析listen函数的过程。该函数是net模块提供的。我们只看关键的代码。

Server.prototype.listen = function(...args) { // 处理入参,根据文档我们知道listen可以接收好几个参数,我们这里是只传了端口号9297 var normalized = normalizeArgs(args); // normalized = [{port: 9297}, null]; var options = normalized[0]; var cb = normalized[1]; // 第一次listen的时候会创建,如果非空说明已经listen过 if (this._handle) {  throw new errors.Error('ERR_SERVER_ALREADY_LISTEN'); } ...... listenInCluster(this, null, options.port | 0, 4,           backlog, undefined, options.exclusive);}function listenInCluster() {  ...  server._listen2(address, port, addressType, backlog, fd);}_listen2 = setupListenHandle = function() {  ......  this._handle = createServerHandle(...);  this._handle.listen(backlog || 511);}function createServerHandle() {  handle = new TCP(TCPConstants.SERVER);  handle.bind(address, port);}

到这我们终于看到了tcp连接的内容,每一个服务器新建一个handle并且保存他,该handle是一个TCP对象。然后执行bind和listen函数。接下来我们就看一下TCP类的代码。TCP是C++提供的类。对应的文件是tcp_wrap.cc。我们看看new TCP的时候发生了什么。

void TCPWrap::New(const FunctionCallbackInfo& args) { // This constructor should not be exposed to public javascript. // Therefore we assert that we are not trying to call this as a // normal function. CHECK(args.IsConstructCall()); CHECK(args[0]->IsInt32()); Environment* env = Environment::GetCurrent(args); int type_value = args[0].As()->Value(); TCPWrap::SocketType type = static_cast(type_value); ProviderType provider; switch (type) {  case SOCKET:   provider = PROVIDER_TCPWRAP;   break;  case SERVER:   provider = PROVIDER_TCPSERVERWRAP;   break;  default:   UNREACHABLE(); } new TCPWrap(env, args.This(), provider);}TCPWrap::TCPWrap(Environment* env, Local object, ProviderType provider)  : ConnectionWrap(env, object, provider) { int r = uv_tcp_init(env->event_loop(), &handle_); CHECK_EQ(r, 0); }

我们看到,new TCP的时候其实是执行libuv的uv_tcp_init函数,初始化一个uv_tcp_t的结构体。首先我们先看一下uv_tcp_t结构体的结构。

uv_tcp_tuv_tcp_t// 初始化一个tcp流的结构体int uv_tcp_init(uv_loop_t* loop, uv_tcp_t* tcp) { // 未指定未指定协议 return uv_tcp_init_ex(loop, tcp, AF_UNSPEC);}int uv_tcp_init_ex(uv_loop_t* loop, uv_tcp_t* tcp, unsigned int flags) { int domain; /* Use the lower 8 bits for the domain */ // 低八位是domain domain = flags & 0xFF; if (domain != AF_INET && domain != AF_INET6 && domain != AF_UNSPEC)  return UV_EINVAL; // 除了第八位的其他位是flags if (flags & ~0xFF)  return UV_EINVAL; uv__stream_init(loop, (uv_stream_t*)tcp, UV_TCP); /* If anything fails beyond this point we need to remove the handle from  * the handle queue, since it was added by uv__handle_init in uv_stream_init.  */ if (domain != AF_UNSPEC) {  int err = maybe_new_socket(tcp, domain, 0);  if (err) {   // 出错则把该handle移除loop队列   QUEUE_REMOVE(&tcp->handle_queue);   return err;  } } return 0;}

我们接着看uv__stream_init做了什么事情。

void uv__stream_init(uv_loop_t* loop,           uv_stream_t* stream,           uv_handle_type type) { int err; uv__handle_init(loop, (uv_handle_t*)stream, type); stream->read_cb = NULL; stream->alloc_cb = NULL; stream->close_cb = NULL; stream->connection_cb = NULL; stream->connect_req = NULL; stream->shutdown_req = NULL; stream->accepted_fd = -1; stream->queued_fds = NULL; stream->delayed_error = 0; QUEUE_INIT(&stream->write_queue); QUEUE_INIT(&stream->write_completed_queue); stream->write_queue_size = 0; if (loop->emfile_fd == -1) {  err = uv__open_cloexec("/dev/null", O_RDONLY);  if (err < 0)    /* In the rare case that "/dev/null" isn't mounted open "/"     * instead.     */    err = uv__open_cloexec("/", O_RDONLY);  if (err >= 0)   loop->emfile_fd = err; }#if defined(__APPLE__) stream->select = NULL;#endif /* defined(__APPLE_) */ // 初始化io观察者 uv__io_init(&stream->io_watcher, uv__stream_io, -1);}void uv__io_init(uv__io_t* w, uv__io_cb cb, int fd) { assert(cb != NULL); assert(fd >= -1); // 初始化队列,回调,需要监听的fd QUEUE_INIT(&w->pending_queue); QUEUE_INIT(&w->watcher_queue); w->cb = cb; w->fd = fd; w->events = 0; w->pevents = 0;#if defined(UV_HAVE_KQUEUE) w->rcount = 0; w->wcount = 0;#endif /* defined(UV_HAVE_KQUEUE) */}

从代码可以知道,只是对uv_tcp_t结构体做了一些初始化操作。到这,new TCP的逻辑就执行完毕了。接下来就是继续分类nodejs里调用bind和listen的逻辑。nodejs的bind对应libuv的函数是uv__tcp_bind,listen对应的是uv_tcp_listen。
先看一个bind的核心代码。

/* Cannot set IPv6-only mode on non-IPv6 socket. */ if ((flags & UV_TCP_IPV6ONLY) && addr->sa_family != AF_INET6)  return UV_EINVAL; // 获取一个socket并且设置某些标记 err = maybe_new_socket(tcp, addr->sa_family, 0); if (err)  return err; on = 1; // 设置在端口可重用 if (setsockopt(tcp->io_watcher.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))  return UV__ERR(errno); bind(tcp->io_watcher.fd, addr, addrlen) && errno != EADDRINUSEstatic int maybe_new_socket(uv_tcp_t* handle, int domain, unsigned long flags) { struct sockaddr_storage saddr; socklen_t slen; if (domain == AF_UNSPEC) {  handle->flags |= flags;  return 0; } return new_socket(handle, domain, flags);}static int new_socket(uv_tcp_t* handle, int domain, unsigned long flags) { struct sockaddr_storage saddr; socklen_t slen; int sockfd; int err; // 获取一个socket err = uv__socket(domain, SOCK_STREAM, 0); if (err < 0)  return err; sockfd = err; // 设置选项和保存socket的文件描述符到io观察者中 err = uv__stream_open((uv_stream_t*) handle, sockfd, flags); if (err) {  uv__close(sockfd);  return err; } ... return 0;}int uv__stream_open(uv_stream_t* stream, int fd, int flags) { if (!(stream->io_watcher.fd == -1 || stream->io_watcher.fd == fd))  return UV_EBUSY; assert(fd >= 0); stream->flags |= flags; if (stream->type == UV_TCP) {  if ((stream->flags & UV_HANDLE_TCP_NODELAY) && uv__tcp_nodelay(fd, 1))   return UV__ERR(errno);  /* TODO Use delay the user passed in. */  if ((stream->flags & UV_HANDLE_TCP_KEEPALIVE) &&    uv__tcp_keepalive(fd, 1, 60)) {   return UV__ERR(errno);  } } ... // 保存socket对应的文件描述符到io观察者中,libuv会在io poll阶段监听该文件描述符 stream->io_watcher.fd = fd; return 0;}

上面的一系列操作主要是新建一个socket文件描述符,设置一些flag,然后把文件描述符保存到IO观察者中,libuv在poll IO阶段会监听该文件描述符,如果有事件到来,会执行设置的回调函数,该函数是在uv__stream_init里设置的uv__stream_io。最后执行bind函数进行绑定操作。最后我们来分析一下listen函数。首先看下tcp_wrapper.cc的代码。

void TCPWrap::Listen(const FunctionCallbackInfo& args) { TCPWrap* wrap; ASSIGN_OR_RETURN_UNWRAP(&wrap,             args.Holder(),             args.GetReturnValue().Set(UV_EBADF)); int backlog = args[0]->Int32Value(); int err = uv_listen(reinterpret_cast(&wrap->handle_),           backlog,           OnConnection); args.GetReturnValue().Set(err);}

代码中有个很重要的地方就是OnConnection函数,nodejs给listen函数设置了一个回调函数OnConnection,该函数在IO观察者里保存的文件描述符有连接到来时会被调用。OnConnection函数是在connection_wrap.cc定义的,tcp_wrapper继承了connection_wrap。下面我们先看一下uv_listen。该函数调用了uv_tcp_listen。该函数的核心代码如下。

 if (listen(tcp->io_watcher.fd, backlog))  return UV__ERR(errno); // cb即OnConnection tcp->connection_cb = cb; tcp->flags |= UV_HANDLE_BOUND; // 有连接到来时的libuv层回调,覆盖了uv_stream_init时设置的值 tcp->io_watcher.cb = uv__server_io; // 注册事件 uv__io_start(tcp->loop, &tcp->io_watcher, POLLIN);

在libuv的poll IO阶段,epoll_wait会监听到到来的连接,然后调用uv__server_io。下面是该函数的核心代码。

// 继续注册事件,等待连接 uv__io_start(stream->loop, &stream->io_watcher, POLLIN); err = uv__accept(uv__stream_fd(stream)); // 保存连接对应的socket stream->accepted_fd = err; // 执行nodejs层回调 stream->connection_cb(stream, 0);

libuv会摘下一个连接,得到对应的socket。然后执行nodejs层的回调,这时候我们来看一下OnConnection的代码。

OnConnection(uv_stream_t* handle,int status)  if (status == 0) {    // 新建一个uv_tcp_t结构体    Local client_obj = WrapType::Instantiate(env, wrap_data, WrapType::SOCKET);    WrapType* wrap;    ASSIGN_OR_RETURN_UNWRAP(&wrap, client_obj);    uv_stream_t* client_handle = reinterpret_cast(&wrap->handle_);    // uv_accept返回0表示成功    if (uv_accept(handle, client_handle))     return;    argv[1] = client_obj; } // 执行上层的回调,该回调是net.js设置的onconnection wrap_data->MakeCallback(env->onconnection_string(), arraysize(argv), argv);

OnConnection新建了一个uv_tcp_t结构体。代表这个连接。然后调用uv_accept。

int uv_accept(uv_stream_t* server, uv_stream_t* client) {  ...  // 新建的uv_tcp_t结构体关联accept_fd,注册读写事件  uv__stream_open(client, server->accepted_fd, UV_HANDLE_READABLE | UV_HANDLE_WRITABLE);  ...}

最后执行nodejs的回调。

function onconnection(err, clientHandle) { var handle = this; var self = handle.owner; if (err) {  self.emit('error', errnoException(err, 'accept'));  return; } if (self.maxConnections && self._connections >= self.maxConnections) {  clientHandle.close();  return; } var socket = new Socket({  handle: clientHandle,  allowHalfOpen: self.allowHalfOpen,  pauseOnCreate: self.pauseOnConnect }); socket.readable = socket.writable = true; self._connections++; socket.server = self; socket._server = self; DTRACE_NET_SERVER_CONNECTION(socket); LTTNG_NET_SERVER_CONNECTION(socket); COUNTER_NET_SERVER_CONNECTION(socket); // 触发_http_server.js里设置的connectionListener回调 self.emit('connection', socket);}

listen函数总体的逻辑就是把socket设置为可监听,然后注册事件,等待连接的到来,连接到来的时候,调用accept获取新建立的连接,tcp_wrapper.cc的回调新建一个uv_tcp_t结构体,代表新的连接,然后设置可读写事件,并且设置回调为uv__stream_io,等待数据的到来。最后执行_http_server.js设置的回调connectionListener。至此,服务器启动并且接收连接的过程就完成了。接下来就是对用户数据的读写。当用户传来数据时,处理数据的函数是uv__stream_io。后面继续解析数据的读写。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家的支持。

您可能感兴趣的文章:

  • node.js中的http.createServer方法使用说明


  • 上一条:
    vue中keep-alive组件的入门使用教程
    下一条:
    jquery+ajax实现上传图片并显示上传进度功能【附php后台接收】
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 使用 Alpine.js 排序插件对元素进行排序(0个评论)
    • 在js中使用jszip + file-saver实现批量下载OSS文件功能示例(0个评论)
    • 在vue中实现父页面按钮显示子组件中的el-dialog效果(0个评论)
    • 使用mock-server实现模拟接口对接流程步骤(0个评论)
    • vue项目打包程序实现把项目打包成一个exe可执行程序(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-03
    • 2018-04
    • 2018-05
    • 2018-06
    • 2018-09
    • 2018-11
    • 2018-12
    • 2019-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-04
    • 2021-05
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客