前几天和一个小伙伴交流了一下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连接内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

nodejs处理tcp连接的核心流程的更多相关文章

  1. 当iOS应用程序进入后台时,TCP和UDP(与多播)连接会发生什么

    我创建了几个实验:设置1:我创建了一个TCPSender应用程序和一个TCPReceiver应用程序.在本次实验中,我在iOS设备上启动了TCPSender,在另一台iOS设备上启动了TCPReceiver.然后两者都经过验证已建立连接并发送和接收数据.然后我将TCPReceiver应用程序置于后台.TCPSender应用程序指示连接丢失和崩溃(是的,我打算这样).设置2:我创建了一个UDPSen

  2. 在iOS中,如何增加主机的HTTP连接限制?

    使用Xcode网络工具,我分析说,我每次只能建立每个主机的4个TCP连接.似乎iOS的每个主机的默认TCP连接限制为4.我们如何增加这个限制?

  3. Swift开发:GCDAsyncSocket通信之TCP服务器

    overridefuncviewDidLoad(){super.viewDidLoad()clientSockets=NSMutableArray()msgTextView.backgroundColor=UIColor.grayColor()msgTextView.text="接收的客户端消息:\n"}//发送消息按钮@IBActionfuncsendBtnClick{ifclientSockets.count==0{return}letmsg=inputTextInput.text!//1.处理请求,

  4. Swift开发:GCDAsyncSocket通信之TCP 客户端

    varmainQueue=dispatch_get_main_queue()overridefuncviewDidLoad(){super.viewDidLoad()}//连接服务器按钮事件@IBActionfuncconBtnClick{do{clientSocket=GCDAsyncSocket()clientSocket.delegate=selfclientSocket.delegateQueue=dispatch_get_global_queue(0,0)tryclientSocket.conn

  5. Android GCM讯息需要太长时间才能到来

    我在应用程序中使用GCM,我有一个问题.大部分时间我马上收到邮件,但有时邮件会在5分钟后再次出现,就像他们被困在路上.这是正常吗?

  6. Android TCP连接最佳做法

    我正在处理一个需要TCP连接到TCP服务器的Android应用程序我的AndroidTCP客户端正在工作可以来回发送消息.我的奇怪问题是:>在Android中处理与服务器的TCP连接的最佳方式是什么?>如何维护连接正确关闭连接)?

  7. NT IIS下用ODBC连接数据库

    $connection=intodbc_connect建立数据库连接,$query_string="查询记录的条件"如:$query_string="select*fromtable"用$cur=intodbc_exec检索数据库,将记录集放入$cur变量中。再用while{$var1=odbc_result;$var2=odbc_result;...}读取odbc_exec()返回的数据集$cur。最后是odbc_close关闭数据库的连接。odbc_result()函数是取当前记录的指定字段值。

  8. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  9. 浅析Nodejs npm常用命令

    这篇文章主要介绍了浅析Nodejs npm常用命令的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下

  10. nodejs 使用nodejs-websocket模块实现点对点实时通讯

    这篇文章主要介绍了nodejs 使用nodejs-websocket模块实现点对点实时通讯的实例代码,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部