上篇文章讲到nodejs创建一个服务并且建立tcp连接的过程。接下来分析一下,在建立tcp连接后,nodejs是如何解析http协议的。我们首先看一下nodejs在建立tcp连接时执行net.js层的回调时做了什么操作。下面是核心代码。
// clientHandle代表一个和客户端建立tcp连接的实体
function onconnection(err, clientHandle) {
var handle = this;
var self = handle.owner;
debug('onconnection');
if (err) {
self.emit('error', errnoException(err, 'accept'));
return;
}
// 建立过多,关掉
if (self.maxConnections && self._connections >= self.maxConnections) {
clientHandle.close();
return;
}
var socket = new Socket({
handle: clientHandle,
allowHalfOpen: self.allowHalfOpen,
pauseOnCreate: self.pauseOnConnect
});
socket.readable = socket.writable = true;
}
...
建立tcp连接后nodejs新建了一个Socket实体。我们看一下new Socket的核心逻辑。
stream.Duplex.call(this, options);
this._handle = options.handle;
initSocketHandle(this);
// 触发底层注册一些函数
this.read(0);
function initSocketHandle(self) {
if (self._handle) {
self._handle.owner = self;
// 这个函数在底层有数据时会回调
self._handle.onread = onread;
self[async_id_symbol] = getNewAsyncId(self._handle);
}
}
另一个重点是read(0)这个函数的逻辑。
Socket.prototype.read = function(n) {
if (n === 0)
return stream.Readable.prototype.read.call(this, n);
this.read = stream.Readable.prototype.read;
this._consuming = true;
return this.read(n);
};
read函数最终会调用_read函数读数据。我们看一下ReadableStream里的read。
在read里会执行_read
this._read(state.highWaterMark);
而_read是由Socket函数实现的。因为Socket继承了ReadableStream。_read执行了一个很重要的操作。
this._handle.readStart();
_handle代表的是一个TCP对象,即tcp_wrapper.cc里创建的。所以我们去看tcp_wrapper的代码。但是没找到该函数。原来该函数在tcp_wrapper的子类stream_wrap里实现的。
int LibuvStreamWrap::ReadStart() {
return uv_read_start(stream(), [](uv_handle_t* handle,
size_t suggested_size,
uv_buf_t* buf) {
static_cast<LibuvStreamWrap*>(handle->data)->OnUvAlloc(suggested_size, buf);
}, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
static_cast<LibuvStreamWrap*>(stream->data)->OnUvRead(nread, buf);
});
}
其实就是调用了libuv的uv_read_start函数。该函数在stream.c里。我们继续往下看。
stream->read_cb = read_cb;
stream->alloc_cb = alloc_cb;
// 注册读事件
uv__io_start(stream->loop, &stream->io_watcher, POLLIN);
主要是注册了事件和回调函数,该函数会在数据到来时被执行。到此,就告一段落了。现在就要等数据的到来。上篇文章我们分析过,数据到来时执行的函数是uv__stream_io。
if (events & (POLLIN | POLLERR | POLLHUP))
uv__read(stream)
有读事件到来的时候,uv__stream_io会调uv_read函数。
buf = uv_buf_init(NULL, 0);
stream->alloc_cb((uv_handle_t*)stream, 64 * 1024, &buf);
if (buf.base == NULL || buf.len == 0) {
/* User indicates it can't or won't handle the read. */
stream->read_cb(stream, UV_ENOBUFS, &buf);
return;
}
这两个函数就是刚才注册的。我们再次回到nodejs的c++代码。看一下这两个函数做了什么。
void LibuvStreamWrap::OnUvRead(...) {
EmitRead(nread, *buf);
}
EmitRead在stream_base-inl.h里定义,他又是一个子类。
inline void StreamResource::EmitRead(ssize_t nread, const uv_buf_t& buf) {
if (nread > 0)
bytes_read_ += static_cast<uint64_t>(nread);
listener_->OnStreamRead(nread, buf);
}
在stream_base.c定义
OnStreamRead() {
stream->CallJSOnreadMethod(nread, obj);
}
CallJSOnreadMethod() {
wrap->MakeCallback(env->onread_string(), arraysize(argv), argv);
}
在env.h里我们知道onread_string就是onread,所以这里就是执行js层的onread函数。该函数就是在new Socket的时候注册的。我们回到js的代码。
function onread() {
var ret = self.push(buffer);
}
push函数是在readableStream里定义的。他经过一系列处理触发ondata事件。
function addChunk(...) {
...
stream.emit('data', chunk);
...
}
那是谁监听了ondata事件呢,我们首先看一下nodejs在建立一个连接到再_http_server.js层做了什么处理。
function Server(requestListener) {
if (!(this instanceof Server)) return new Server(requestListener);
net.Server.call(this, {
allowHalfOpen: true });
// 收到http请求时执行的回调
if (requestListener) {
this.on('request', requestListener);
}
this.httpAllowHalfOpen = false;
// 建立tcp连接的回调
this.on('connection', connectionListener);
this.timeout = 2 * 60 * 1000;
this.keepAliveTimeout = 5000;
this._pendingResponseData = 0;
this.maxHeadersCount = null;
}
connectionListener代码如下。
function connectionListener(socket) {
defaultTriggerAsyncIdScope(
getOrSetAsyncId(socket), connectionListenerInternal, this, socket
);
}
function connectionListenerInternal(server, socket) {
httpSocketSetup(socket);
if (socket.server === null)
socket.server = server;
if (server.timeout && typeof socket.setTimeout === 'function')
socket.setTimeout(server.timeout);
socket.on('timeout', socketOnTimeout);
var parser = parsers.alloc();
parser.reinitialize(HTTPParser.REQUEST);
parser.socket = socket;
socket.parser = parser;
parser.incoming = null;
// Propagate headers limit from server instance to parser
if (typeof server.maxHeadersCount === 'number') {
parser.maxHeaderPairs = server.maxHeadersCount << 1;
} else {
// Set default value because parser may be reused from FreeList
parser.maxHeaderPairs = 2000;
}
var state = {
onData: null,
onEnd: null,
onClose: null,
onDrain: null,
outgoing: [],
incoming: [],
outgoingData: 0,
keepAliveTimeoutSet: false
};
// 收到tcp连接中的数据时回调
state.onData = socketOnData.bind(undefined, server, socket, parser, state);
state.onEnd = socketOnEnd.bind(undefined, server, socket, parser, state);
state.onClose = socketOnClose.bind(undefined, socket, state);
state.onDrain = socketOnDrain.bind(undefined, socket, state);
socket.on('data', state.onData);
socket.on('error', socketOnError);
socket.on('end', state.onEnd);
socket.on('close', state.onClose);
socket.on('drain', state.onDrain);
parser.onIncoming = parserOnIncoming.bind(undefined, server, socket, state);
// We are consuming socket, so it won't get any actual data
socket.on('resume', onSocketResume);
socket.on('pause', onSocketPause);
// Override on to unconsume on `data`, `readable` listeners
socket.on = socketOnWrap;
// We only consume the socket if it has never been consumed before.
if (socket._handle) {
var external = socket._handle._externalStream;
if (!socket._handle._consumed && external) {
parser._consumed = true;
socket._handle._consumed = true;
parser.consume(external);
}
}
parser[kOnExecute] =
onParserExecute.bind(undefined, server, socket, parser, state);
socket._paused = false;
}
主要是注册了一系列的回调函数,这些函数在收到数据或者解析数据时会被执行。所以收到数据后执行的函数是socketOnData。该函数就是把数据传进http解析器然后进行解析。
function socketOnData(server, socket, parser, state, d) {
...
var ret = parser.execute(d);
onParserExecuteCommon(server, socket, parser, state, ret, d);
}
我们先看一下parser是个什么。parser是在_http_server.js的onconnection回调里,parsers.alloc()分配的。而parsers又是个啥呢?他在_http_common.js里定义。
var parsers = new FreeList('parsers', 1000, function() {
var parser = new HTTPParser(HTTPParser.REQUEST);
parser._headers = [];
parser._url = '';
parser._consumed = false;
parser.socket = null;
parser.incoming = null;
parser.outgoing = null;
// Only called in the slow case where slow means
// that the request headers were either fragmented
// across multiple TCP packets or too large to be
// processed in a single run. This method is also
// called to process trailing HTTP headers.
parser[kOnHeaders] = parserOnHeaders;
parser[kOnHeadersComplete] = parserOnHeadersComplete;
parser[kOnBody] = parserOnBody;
parser[kOnMessageComplete] = parserOnMessageComplete;
parser[kOnExecute] = null;
return parser;
});
class FreeList {
constructor(name, max, ctor) {
this.name = name;
this.ctor = ctor;
this.max = max;
this.list = [];
}
alloc() {
return this.list.length ?
this.list.pop() :
this.ctor.apply(this, arguments);
}
free(obj) {
if (this.list.length < this.max) {
this.list.push(obj);
return true;
}
return false;
}
}
他其实是管理http解析器的。重点是HTTPParser,他定义在node_http_parser.cc是对http解析器的封装。真正的解析器在http_parser.c。回到刚才的地方。nodejs收到数据后执行 parser.execute(d);execute函数对应的是node_http_parser里的Execute。该函数进行了重载。入口是下面这个函数。
static void Execute(const FunctionCallbackInfo<Value>& args) {
Local<Value> ret = parser->Execute(buffer_data, buffer_len);
}
Local<Value> Execute(char* data, size_t len) {
http_parser_execute(&parser_, &settings, data, len);
}
http_parser_execute函数定义在http_parser.c,该函数就是进行真正的http协议解析。它里面会有一些钩子函数。在解析的某个阶段会执行。例如解析完头部。
if (settings->on_headers_complete) {
switch (settings->on_headers_complete(parser)) {
...
}
}
具体的定义在node_http_parser.cc
const struct http_parser_settings Parser::settings = {
Proxy<Call, &Parser::on_message_begin>::Raw,
Proxy<DataCall, &Parser::on_url>::Raw,
Proxy<DataCall, &Parser::on_status>::Raw,
Proxy<DataCall, &Parser::on_header_field>::Raw,
Proxy<DataCall, &Parser::on_header_value>::Raw,
Proxy<Call, &Parser::on_headers_complete>::Raw,
Proxy<DataCall, &Parser::on_body>::Raw,
Proxy<Call, &Parser::on_message_complete>::Raw,
nullptr, // on_chunk_header
nullptr // on_chunk_complete
};
这里我们以on_header_complete钩子来分析。
const uint32_t kOnHeadersComplete = 1
int on_headers_complete() {
Local<Value> cb = obj->Get(kOnHeadersComplete);
MakeCallback(cb.As<Function>(), arraysize(argv), argv);
}
最后会执行kOnHeadersComplete这个函数。我们看到这个kOnHeadersComplete 等于1,其实这个是在js层复赋值的。在_http_common.js中的开头。
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
然后在新建一个http解析器的函数注册了该函数。
parser[kOnHeadersComplete] = parserOnHeadersComplete;
所以当解析头部结束就会执行parserOnHeadersComplete。
function parserOnHeadersComplete(...) {
parser.incoming = new IncomingMessage(parser.socket);
...
return parser.onIncoming(parser.incoming, shouldKeepAlive);
}
新建了一个IncomingMessage对象,然后执行_http_server.js注册的回调onIncoming 。该回调函数也是再建立tcp连接时注册的。
function parserOnIncoming() {
var res = new ServerResponse(req);
...
server.emit('request', req, res);
}
生成一个ServerResponse对象,然后触发request事件。该函数是在我们执行http.createServer时传进行的函数。
function Server(requestListener) {
...
// 收到http请求时执行的回调
if (requestListener) {
this.on('request', requestListener);
}
}
最后在我们的回调里就拿到了这两个对象。但是这时候只是解析完了头部,request对象里还拿不到body的数据。我们需要自己获取。
var str = "";
req.on('data', (data) => {
str += data;
});
req.on('end',() => {
})
文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态
文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境
文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn
文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker
文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机
文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk
文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入
文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。 Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。
文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动
文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计
文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;gt;Jni-&amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图
文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法