nodejs处理tcp连接的核心流程


Posted in NodeJs onFebruary 26, 2021

前几天和一个小伙伴交流了一下nodejs中epoll和处理请求的一些知识,今天简单来聊一下nodejs处理请求的逻辑。我们从listen函数开始。

int uv_tcp_listen(uv_tcp_t* tcp, int backlog, uv_connection_cb cb) {
 // 设置处理的请求的策略,见下面的分析
 if (single_accept == -1) {
  const char* val = getenv("UV_TCP_SINGLE_ACCEPT");
  single_accept = (val != NULL && atoi(val) != 0); /* Off by default. */
 }
 if (single_accept)
  tcp->flags |= UV_HANDLE_TCP_SINGLE_ACCEPT;
 // 执行bind或设置标记
 err = maybe_new_socket(tcp, AF_INET, flags);
 // 开始监听
 if (listen(tcp->io_watcher.fd, backlog))
  return UV__ERR(errno);
 // 设置回调
 tcp->connection_cb = cb;
 tcp->flags |= UV_HANDLE_BOUND;
 // 设置io观察者的回调,由epoll监听到连接到来时执行
 tcp->io_watcher.cb = uv__server_io;
 // 插入观察者队列,这时候还没有增加到epoll,poll io阶段再遍历观察者队列进行处理(epoll_ctl)
 uv__io_start(tcp->loop, &tcp->io_watcher, POLLIN);

 return 0;
}

我们看到,当我们createServer的时候,到Libuv层就是传统的网络编程的逻辑。这时候我们的服务就启动了。在poll io阶段,我们的监听型的文件描述符和上下文(感兴趣的事件、回调等)就会注册到epoll中。正常来说就阻塞在epoll。那么这时候有一个tcp连接到来,会怎样呢?epoll首先遍历触发了事件的fd,然后执行fd上下文中的回调,即uvserver_io。我们看看uvserver_io。

void uv__server_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
 // 循环处理,uv__stream_fd(stream)为服务器对应的fd
 while (uv__stream_fd(stream) != -1) {
  // 通过accept拿到和客户端通信的fd,我们看到这个fd和服务器的fd是不一样的
  err = uv__accept(uv__stream_fd(stream));
  // uv__stream_fd(stream)对应的fd是非阻塞的,返回这个错说明没有连接可用accept了,直接返回
  if (err < 0) {
   if (err == UV_EAGAIN || err == UV__ERR(EWOULDBLOCK))
    return;
  }
  // 记录下来
  stream->accepted_fd = err;
  // 执行回调
  stream->connection_cb(stream, 0);
  /*
   stream->accepted_fd为-1说明在回调connection_cb里已经消费了accepted_fd,
   否则先注销服务器在epoll中的fd的读事件,等待消费后再注册,即不再处理请求了
  */
  if (stream->accepted_fd != -1) {
   uv__io_stop(loop, &stream->io_watcher, POLLIN);
   return;
  }
 /*
   ok,accepted_fd已经被消费了,我们是否还要继续accept新的fd,
   如果设置了UV_HANDLE_TCP_SINGLE_ACCEPT,表示每次只处理一个连接,然后
   睡眠一会,给机会给其他进程accept(多进程架构时)。如果不是多进程架构,又设置这个,
   就会导致处理连接被延迟了一下
 */
  if (stream->type == UV_TCP &&
    (stream->flags & UV_HANDLE_TCP_SINGLE_ACCEPT)) {
   struct timespec timeout = { 0, 1 };
   nanosleep(&timeout, NULL);
  }
 }
}

从uv__server_io,我们知道Libuv在一个循环中不断accept新的fd,然后执行回调,正常来说,回调会消费fd,如此循环,直到没有连接可处理了。接下来,我们重点看看回调里是如何消费fd的,大量的循环会不会消耗过多时间导致Libuv的事件循环被阻塞一会。tcp的回调是c++层的OnConnection。

// 有连接时触发的回调
template <typename WrapType, typename UVType>
void ConnectionWrap<WrapType, UVType>::OnConnection(uv_stream_t* handle,
                          int status) {
 // 拿到Libuv结构体对应的c++层对象                          
 WrapType* wrap_data = static_cast<WrapType*>(handle->data);
 CHECK_EQ(&wrap_data->handle_, reinterpret_cast<UVType*>(handle));

 Environment* env = wrap_data->env();
 HandleScope handle_scope(env->isolate());
 Context::Scope context_scope(env->context());

 // 和客户端通信的对象
 Local<Value> client_handle;

 if (status == 0) {
  // Instantiate the client javascript object and handle.
  // 新建一个js层使用对象
  Local<Object> client_obj;
  if (!WrapType::Instantiate(env, wrap_data, WrapType::SOCKET)
       .ToLocal(&client_obj))
   return;

  // Unwrap the client javascript object.
  WrapType* wrap;
  // 把js层使用的对象client_obj所对应的c++层对象存到wrap中
  ASSIGN_OR_RETURN_UNWRAP(&wrap, client_obj);
  // 拿到对应的handle
  uv_stream_t* client = reinterpret_cast<uv_stream_t*>(&wrap->handle_);
  // 从handleaccpet到的fd中拿一个保存到client,client就可以和客户端通信了
  if (uv_accept(handle, client))
   return;
  client_handle = client_obj;
 } else {
  client_handle = Undefined(env->isolate());
 }
 // 回调js,client_handle相当于在js层执行new TCP
 Local<Value> argv[] = { Integer::New(env->isolate(), status), client_handle };
 wrap_data->MakeCallback(env->onconnection_string(), arraysize(argv), argv);
}

代码看起来很复杂,我们只需要关注uv_accept。uv_accept的参数,第一个是服务器对应的handle,第二个是表示和客户端通信的对象。

int uv_accept(uv_stream_t* server, uv_stream_t* client) {
 int err;

 switch (client->type) {
  case UV_NAMED_PIPE:
  case UV_TCP:
   // 把fd设置到client中
   err = uv__stream_open(client,
              server->accepted_fd,
              UV_HANDLE_READABLE | UV_HANDLE_WRITABLE);
   break;
 // ...
 }

 client->flags |= UV_HANDLE_BOUND;
 // 标记已经消费了fd
 server->accepted_fd = -1;
 return err;
}

uv_accept主要就是两个逻辑,把和客户端通信的fd设置到client中,并标记已经消费,从而驱动刚才讲的while循环继续执行。对于上层来说,就是拿到了一个和客户端的对象,在Libuv层是结构体,在c++层是一个c++对象,在js层是一个js对象,他们三个是一层层封装且关联起来的,最核心的是Libuv的client结构体中的fd,这是和客户端通信的底层门票。最后回调js层,那就是执行net.js的onconnection。onconnection又封装了一个Socket对象用于表示和客户端通信,他持有c++层的对象,c++层对象又持有Libuv的结构体,Libuv结构体又持有fd。

const socket = new Socket({
  handle: clientHandle,
  allowHalfOpen: self.allowHalfOpen,
  pauseOnCreate: self.pauseOnConnect,
  readable: true,
  writable: true
 });

到此这篇关于nodejs处理tcp连接的核心流程的文章就介绍到这了,更多相关nodejs处理tcp连接内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

NodeJs 相关文章推荐
nodejs命令行参数处理模块commander使用实例
Sep 17 NodeJs
基于NodeJS的前后端分离的思考与实践(四)安全问题解决方案
Sep 26 NodeJs
轻松创建nodejs服务器(9):实现非阻塞操作
Dec 18 NodeJs
Windows 系统下设置Nodejs NPM全局路径
Apr 26 NodeJs
Nodejs全局安装和本地安装的不同之处
Jul 04 NodeJs
NodeJS中的MongoDB快速入门详细教程
Nov 11 NodeJs
详解nodejs 文本操作模块-fs模块(三)
Dec 22 NodeJs
NodeJS学习笔记之Module的简介
Mar 24 NodeJs
Nodejs中Express 常用中间件 body-parser 实现解析
May 22 NodeJs
Nodejs技巧之Exceljs表格操作用法示例
Nov 06 NodeJs
nodejs nedb 封装库与使用方法示例
Feb 06 NodeJs
使用nodejs实现JSON文件自动转Excel的工具(推荐)
Jun 24 NodeJs
Nodejs 数组的队列以及forEach的应用详解
Feb 25 #NodeJs
一文秒懂nodejs中的异步编程
Jan 28 #NodeJs
在nodejs中创建child process的方法
Jan 26 #NodeJs
nodejs中使用worker_threads来创建新的线程的方法
Jan 22 #NodeJs
Nodejs 微信小程序消息推送的实现
Jan 20 #NodeJs
Nodejs实现微信分账的示例代码
Jan 19 #NodeJs
nodejs中的异步编程知识点详解
Jan 17 #NodeJs
You might like
非常好的php目录导航文件代码
2006/10/09 PHP
PHP 批量删除 sql语句
2009/06/05 PHP
php实现可运算的验证码
2015/11/10 PHP
php微信公众账号开发之前五个坑(一)
2016/09/18 PHP
laravel-admin的多级联动方法
2019/09/30 PHP
给网站上的广告“加速”显示的方法
2007/04/08 Javascript
javascript 写的一个简单的timer
2009/07/30 Javascript
file模式访问网页时iframe高度自适应解决方案
2013/01/16 Javascript
javascript标签在页面中的位置探讨
2013/04/11 Javascript
jquery插件jquery倒计时插件分享
2013/12/27 Javascript
JQuery下拉框应用示例介绍
2014/04/23 Javascript
Jquery设置attr的disabled属性控制某行显示或者隐藏
2014/09/25 Javascript
jQuery实现带有上下控制按钮的简单多行滚屏效果代码
2015/09/04 Javascript
JS实现光滑展开合拢的菜单效果代码
2015/09/16 Javascript
AngularJS指令与控制器之间的交互功能示例
2016/12/14 Javascript
angularjs实现的前端分页控件示例
2017/02/10 Javascript
微信小程序自定义toast弹窗效果的实现代码
2018/11/15 Javascript
2019 年编写现代 JavaScript 代码的5个小技巧(小结)
2019/01/15 Javascript
JS使用cookie保存用户登录信息操作示例
2019/05/30 Javascript
vue语法自动转typescript(解放双手)
2019/09/18 Javascript
jQuery实现form表单基于ajax无刷新提交方法实例代码
2019/11/04 jQuery
vue ajax 拦截原理与实现方法示例
2019/11/29 Javascript
vue实现短信验证码登录功能(流程详解)
2019/12/10 Javascript
Python OpenCV获取视频的方法
2018/02/28 Python
Python配置虚拟环境图文步骤
2019/05/20 Python
在pycharm中显示python画的图方法
2019/08/31 Python
Python操作SQLite数据库过程解析
2019/09/02 Python
Python json读写方式和字典相互转化
2020/04/18 Python
详解pycharm连接远程linux服务器的虚拟环境的方法
2020/11/13 Python
纯css3实现照片墙效果
2014/12/26 HTML / CSS
如何使用html5与css3完成google涂鸦动画
2012/12/16 HTML / CSS
Html5原创俄罗斯方块(基于canvas)
2019/01/07 HTML / CSS
介绍一下gcc特性
2015/10/31 面试题
工商局副局长个人对照检查材料
2014/09/25 职场文书
志愿者个人总结
2015/03/03 职场文书
《日月潭》教学反思
2016/02/20 职场文书