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服务器(9):实现非阻塞操作
Dec 18 NodeJs
nodejs URL模块操作URL相关方法介绍
Mar 03 NodeJs
nodejs开发微博实例
Mar 25 NodeJs
nodejs的HTML分析利器node-jquery用法浅析
Nov 08 NodeJs
nodejs接入阿里大鱼短信验证码的方法
Jul 10 NodeJs
深入学习nodejs中的async模块的使用方法
Jul 12 NodeJs
nodejs实现截取上传视频中一帧作为预览图片
Dec 10 NodeJs
nodejs 使用 js 模块的方法实例详解
Dec 04 NodeJs
nodejs和react实现即时通讯简易聊天室功能
Aug 21 NodeJs
Nodejs libuv运行原理详解
Aug 21 NodeJs
通过实例了解Nodejs模块系统及require机制
Jul 16 NodeJs
使用 Koa + TS + ESLlint 搭建node服务器的过程详解
May 30 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
正则表达式语法
2006/10/09 Javascript
一个基于PDO的数据库操作类(新) 一个PDO事务实例
2011/07/03 PHP
php判断数组元素中是否存在某个字符串的方法
2014/06/14 PHP
利用Fix Rss Feeds插件修复WordPress的Feed显示错误
2015/12/19 PHP
php格式文件打开的四种方法
2018/02/24 PHP
javascript引用赋值(地址传值)用法实例
2015/01/13 Javascript
JavaScript实现添加及删除事件的方法小结
2015/08/04 Javascript
jQuery中ajax的load()与post()方法实例详解
2016/01/05 Javascript
jQuery Validate表单验证插件 添加class属性形式的校验
2016/01/18 Javascript
玩转NODE.JS(四)-搭建简单的聊天室的代码
2016/11/11 Javascript
关于Node.js中Buffer的一些你可能不知道的用法
2017/03/28 Javascript
JS触摸事件、手势事件详解
2017/05/04 Javascript
bootstrap daterangepicker双日历时间段选择控件详解
2017/06/15 Javascript
JS对象序列化成json数据和json数据转化为JS对象的代码
2017/08/23 Javascript
vue 的keep-alive缓存功能的实现
2018/03/22 Javascript
在vue中给列表中的奇数行添加class的实现方法
2018/09/05 Javascript
Windows下支持自动更新的Electron应用脚手架的方法
2018/12/24 Javascript
electron-vue利用webpack打包实现多页面的入口文件问题
2019/05/12 Javascript
一篇文章带你从零快速上手Rollup
2020/09/07 Javascript
python下如何查询CS反恐精英的服务器信息
2017/01/17 Python
使用Python抓取豆瓣影评数据的方法
2018/10/17 Python
python利用跳板机ssh远程连接redis的方法
2019/02/19 Python
使用python实现简单五子棋游戏
2019/06/18 Python
Python 类,property属性(简化属性的操作),@property,property()用法示例
2019/10/12 Python
python turtle工具绘制四叶草的实例分享
2020/02/14 Python
Macbook安装Python最新版本、GUI开发环境、图像处理、视频处理环境详解
2020/02/17 Python
使用Python发现隐藏的wifi
2020/03/04 Python
Pytest测试框架基本使用方法详解
2020/11/25 Python
Python3.9最新版下载与安装图文教程详解(Windows系统为例)
2020/11/28 Python
阿迪达斯丹麦官网:adidas丹麦
2016/10/01 全球购物
Set里的元素是不能重复的,那么用什么方法来区分重复与否呢?
2016/08/18 面试题
关于旷工的检讨书
2014/02/02 职场文书
企业优秀团员事迹材料
2014/08/20 职场文书
2015人事行政工作总结范文
2015/05/21 职场文书
简单且有用的Python数据分析和机器学习代码
2021/07/02 Python
Apache SeaTunnel实现 非CDC数据抽取
2022/05/20 Servers