nodejs处理tcp连接的核心流程
作者:mrr 发布时间:2024-05-03 15:55:40
前几天和一个小伙伴交流了一下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
});
猜你喜欢
- python虽然是一门'慢语言',但是也有着比较多的性能检测工具来帮助我们优化程序的运行效率。这里总结了五个比较好的pyth
- 测试用例我们分别在用户数据库(testpage),tempdb中创建相似对象t1,#t1,并在tempdb中创建创建非临时表,然后执行相应的
- 我们知道,Diango 接收的 HTTP 请求信息里带有 Cookie 信息。Cookie的作用是为了识别当前用户的身份,通过以下例子来说明
- GO通道和 sync 包的分享我们一起回顾一下上次分享的内容:GO协程同步若不做限制的话,会产生数据竞态的问题我们用锁的方式来解决如上问题,
- 如果是在Oracle10g之前,删除一个表空间中的数据文件后,其文件在数据库数据字典中会仍然存在,除非你删除表空间,否则文件信息不会清除。但
- 1.什么是SQL语句sql语言:结构化的查询语言。(Structured Query Language),是关系数据库管理系统的标准语言。它
- 1. 可选链从 v3.7 可用这是当你尝试访问嵌套数据时的一个痛点,嵌套数据越多,代码就会变得越繁琐。在下面的例子中,要访问address,
- new fun的执行过程分析,学习面向对象的朋友可以参考下。(1)创建一个新的对象,并让this指针指向它;(2)将函数的prototype
- table a(id, type):id type --------------------
- 目录前言1、背景2、模拟测试3、结论总结前言如果不是踩到坑,我估计到现在还不知道时间字段会四舍五入。1、背景通过 Java 代码获取当日最大
- 问题: jsp中想要输出的中文被显示成“?” 解决方法 : 在eclipse-windows- preferences中 搜索jsp , E
- 本文实例为大家分享SQL SERVER数据库备份的具体代码,供大家参考,具体内容如下/** 批量循环备份用户数据库,做为数据库迁
- Django中提供了一个类Paginator专门用来管理和处理分页数据,所以我们在使用之前先导入好相应的类,,另外这里我们也导入了待会会用到
- IE下专属CSS:<![if !IE]><link rel="stylesheet" type=&qu
- golang 空结构体 struct{} 可以用来节省内存a := struct{}{}println(unsafe.Sizeof(a))/
- 1)忘记在 if , elif , else , for , while , class ,def 声明末尾添加 :(导致 “SyntaxE
- using System;using System.Collections.Generic;using System.Linq;using
- 本文实例讲述了php实现搜索一维数组元素并删除二维数组对应元素的方法。分享给大家供大家参考。具体如下:定义一个一维数组一个二维数组如下$fr
- 实例的背景说明假定一个个人信息系统,需要记录系统中各个人的故乡、居住地、以及到过的城市。数据库设计如下:Models.py 内容如下:&nb
- 例如,如果列a被定义为unique,并且值为1,则下列语句有同样的效果,也就是说一旦出入的记录中存在a=1的情况,直接更新c = c + 1