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实用示例 缩址还原
Dec 28 NodeJs
用nodejs写的一个简单项目打包工具
May 11 NodeJs
Nodejs全栈框架StrongLoop推荐
Nov 09 NodeJs
浅谈NodeJS中require路径问题
May 07 NodeJs
nodejs基础应用
Feb 03 NodeJs
Nodejs多站点切换Htpps协议详解及简单实例
Feb 23 NodeJs
NodeJS仿WebApi路由示例
Feb 28 NodeJs
详解nodejs模板引擎制作
Jun 14 NodeJs
nodejs 日志模块winston的使用方法
May 02 NodeJs
Nodejs模块的调用操作实例分析
Dec 25 NodeJs
Nodejs让异步变成同步的方法
Mar 02 NodeJs
nodejs+koa2 实现模仿springMVC框架
Oct 21 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
用Zend Encode编写开发PHP程序
2006/10/09 PHP
最新的php 文件上传模型,支持多文件上传
2009/08/13 PHP
php设计模式 DAO(数据访问对象模式)
2011/06/26 PHP
PHP 读取大文件并显示的简单实例(推荐)
2016/08/12 PHP
Laravel框架表单验证操作实例分析
2019/09/30 PHP
javascript轻松实现当鼠标移开时已弹出子菜单自动消失
2013/12/29 Javascript
Jquery实现由下向上展开效果的例子
2014/12/08 Javascript
JavaScript实现鼠标滑过处生成气泡的方法
2015/05/16 Javascript
Angularjs 实现一个幻灯片示例代码
2016/09/08 Javascript
js自定义Tab选项卡效果
2017/06/05 Javascript
微信小程序 密码输入(源码下载)
2017/06/27 Javascript
JavaScript异步上传图片文件的实例代码
2017/07/04 Javascript
javascript闭包的使用之按钮切换功能
2018/08/30 Javascript
详解微信小程序回到顶部的两种方式
2019/05/09 Javascript
JSX在render函数中的应用详解
2019/09/04 Javascript
js中switch语句的学习笔记
2020/03/25 Javascript
[01:02]DOTA2上海特锦赛SHOWOPEN
2016/03/25 DOTA
Python and、or以及and-or语法总结
2015/04/14 Python
Python存取XML的常见方法实例分析
2017/03/21 Python
78行Python代码实现现微信撤回消息功能
2018/07/26 Python
Python实现处理逆波兰表达式示例
2018/07/30 Python
Python 访问限制 private public的详细介绍
2018/10/16 Python
利用pyshp包给shapefile文件添加字段的实例
2019/12/06 Python
在tensorflow中实现屏蔽输出的log信息
2020/02/04 Python
Python print不能立即打印的解决方式
2020/02/19 Python
将pymysql获取到的数据类型是tuple转化为pandas方式
2020/05/15 Python
django 模型中的计算字段实例
2020/05/19 Python
利用python绘制中国地图(含省界、河流等)
2020/09/21 Python
意大利香水和彩妆护肤品购物网站:Ditano
2017/08/13 全球购物
外贸英语毕业生自荐信
2013/11/14 职场文书
门卫工作岗位职责
2013/12/17 职场文书
毕业生就业协议书
2014/04/11 职场文书
2014乡镇党委副书记对照检查材料思想汇报
2014/10/09 职场文书
领导工作表现评语
2015/01/04 职场文书
给校长的建议书作文300字
2015/09/14 职场文书
2019年大学生暑期社会实践调查报告模板
2019/11/07 职场文书