nodejs處理tcp連接的核心流程
前幾天和一個(gè)小伙伴交流了一下nodejs中epoll和處理請(qǐng)求的一些知識(shí),今天簡(jiǎn)單來(lái)聊一下nodejs處理請(qǐng)求的邏輯。我們從listen函數(shù)開(kāi)始。
int uv_tcp_listen(uv_tcp_t* tcp, int backlog, uv_connection_cb cb) {
// 設(shè)置處理的請(qǐng)求的策略,見(jiàn)下面的分析
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;
// 執(zhí)行bind或設(shè)置標(biāo)記
err = maybe_new_socket(tcp, AF_INET, flags);
// 開(kāi)始監(jiān)聽(tīng)
if (listen(tcp->io_watcher.fd, backlog))
return UV__ERR(errno);
// 設(shè)置回調(diào)
tcp->connection_cb = cb;
tcp->flags |= UV_HANDLE_BOUND;
// 設(shè)置io觀察者的回調(diào),由epoll監(jiān)聽(tīng)到連接到來(lái)時(shí)執(zhí)行
tcp->io_watcher.cb = uv__server_io;
// 插入觀察者隊(duì)列,這時(shí)候還沒(méi)有增加到epoll,poll io階段再遍歷觀察者隊(duì)列進(jìn)行處理(epoll_ctl)
uv__io_start(tcp->loop, &tcp->io_watcher, POLLIN);
return 0;
}
我們看到,當(dāng)我們createServer的時(shí)候,到Libuv層就是傳統(tǒng)的網(wǎng)絡(luò)編程的邏輯。這時(shí)候我們的服務(wù)就啟動(dòng)了。在poll io階段,我們的監(jiān)聽(tīng)型的文件描述符和上下文(感興趣的事件、回調(diào)等)就會(huì)注冊(cè)到epoll中。正常來(lái)說(shuō)就阻塞在epoll。那么這時(shí)候有一個(gè)tcp連接到來(lái),會(huì)怎樣呢?epoll首先遍歷觸發(fā)了事件的fd,然后執(zhí)行fd上下文中的回調(diào),即uvserver_io。我們看看uvserver_io。
void uv__server_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
// 循環(huán)處理,uv__stream_fd(stream)為服務(wù)器對(duì)應(yīng)的fd
while (uv__stream_fd(stream) != -1) {
// 通過(guò)accept拿到和客戶端通信的fd,我們看到這個(gè)fd和服務(wù)器的fd是不一樣的
err = uv__accept(uv__stream_fd(stream));
// uv__stream_fd(stream)對(duì)應(yīng)的fd是非阻塞的,返回這個(gè)錯(cuò)說(shuō)明沒(méi)有連接可用accept了,直接返回
if (err < 0) {
if (err == UV_EAGAIN || err == UV__ERR(EWOULDBLOCK))
return;
}
// 記錄下來(lái)
stream->accepted_fd = err;
// 執(zhí)行回調(diào)
stream->connection_cb(stream, 0);
/*
stream->accepted_fd為-1說(shuō)明在回調(diào)connection_cb里已經(jīng)消費(fèi)了accepted_fd,
否則先注銷服務(wù)器在epoll中的fd的讀事件,等待消費(fèi)后再注冊(cè),即不再處理請(qǐng)求了
*/
if (stream->accepted_fd != -1) {
uv__io_stop(loop, &stream->io_watcher, POLLIN);
return;
}
/*
ok,accepted_fd已經(jīng)被消費(fèi)了,我們是否還要繼續(xù)accept新的fd,
如果設(shè)置了UV_HANDLE_TCP_SINGLE_ACCEPT,表示每次只處理一個(gè)連接,然后
睡眠一會(huì),給機(jī)會(huì)給其他進(jìn)程accept(多進(jìn)程架構(gòu)時(shí))。如果不是多進(jìn)程架構(gòu),又設(shè)置這個(gè),
就會(huì)導(dǎo)致處理連接被延遲了一下
*/
if (stream->type == UV_TCP &&
(stream->flags & UV_HANDLE_TCP_SINGLE_ACCEPT)) {
struct timespec timeout = { 0, 1 };
nanosleep(&timeout, NULL);
}
}
}
從uv__server_io,我們知道Libuv在一個(gè)循環(huán)中不斷accept新的fd,然后執(zhí)行回調(diào),正常來(lái)說(shuō),回調(diào)會(huì)消費(fèi)fd,如此循環(huán),直到?jīng)]有連接可處理了。接下來(lái),我們重點(diǎn)看看回調(diào)里是如何消費(fèi)fd的,大量的循環(huán)會(huì)不會(huì)消耗過(guò)多時(shí)間導(dǎo)致Libuv的事件循環(huán)被阻塞一會(huì)。tcp的回調(diào)是c++層的OnConnection。
// 有連接時(shí)觸發(fā)的回調(diào)
template <typename WrapType, typename UVType>
void ConnectionWrap<WrapType, UVType>::OnConnection(uv_stream_t* handle,
int status) {
// 拿到Libuv結(jié)構(gòu)體對(duì)應(yīng)的c++層對(duì)象
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());
// 和客戶端通信的對(duì)象
Local<Value> client_handle;
if (status == 0) {
// Instantiate the client javascript object and handle.
// 新建一個(gè)js層使用對(duì)象
Local<Object> client_obj;
if (!WrapType::Instantiate(env, wrap_data, WrapType::SOCKET)
.ToLocal(&client_obj))
return;
// Unwrap the client javascript object.
WrapType* wrap;
// 把js層使用的對(duì)象client_obj所對(duì)應(yīng)的c++層對(duì)象存到wrap中
ASSIGN_OR_RETURN_UNWRAP(&wrap, client_obj);
// 拿到對(duì)應(yīng)的handle
uv_stream_t* client = reinterpret_cast<uv_stream_t*>(&wrap->handle_);
// 從handleaccpet到的fd中拿一個(gè)保存到client,client就可以和客戶端通信了
if (uv_accept(handle, client))
return;
client_handle = client_obj;
} else {
client_handle = Undefined(env->isolate());
}
// 回調(diào)js,client_handle相當(dāng)于在js層執(zhí)行new TCP
Local<Value> argv[] = { Integer::New(env->isolate(), status), client_handle };
wrap_data->MakeCallback(env->onconnection_string(), arraysize(argv), argv);
}
代碼看起來(lái)很復(fù)雜,我們只需要關(guān)注uv_accept。uv_accept的參數(shù),第一個(gè)是服務(wù)器對(duì)應(yīng)的handle,第二個(gè)是表示和客戶端通信的對(duì)象。
int uv_accept(uv_stream_t* server, uv_stream_t* client) {
int err;
switch (client->type) {
case UV_NAMED_PIPE:
case UV_TCP:
// 把fd設(shè)置到client中
err = uv__stream_open(client,
server->accepted_fd,
UV_HANDLE_READABLE | UV_HANDLE_WRITABLE);
break;
// ...
}
client->flags |= UV_HANDLE_BOUND;
// 標(biāo)記已經(jīng)消費(fèi)了fd
server->accepted_fd = -1;
return err;
}
uv_accept主要就是兩個(gè)邏輯,把和客戶端通信的fd設(shè)置到client中,并標(biāo)記已經(jīng)消費(fèi),從而驅(qū)動(dòng)剛才講的while循環(huán)繼續(xù)執(zhí)行。對(duì)于上層來(lái)說(shuō),就是拿到了一個(gè)和客戶端的對(duì)象,在Libuv層是結(jié)構(gòu)體,在c++層是一個(gè)c++對(duì)象,在js層是一個(gè)js對(duì)象,他們?nèi)齻€(gè)是一層層封裝且關(guān)聯(lián)起來(lái)的,最核心的是Libuv的client結(jié)構(gòu)體中的fd,這是和客戶端通信的底層門票。最后回調(diào)js層,那就是執(zhí)行net.js的onconnection。onconnection又封裝了一個(gè)Socket對(duì)象用于表示和客戶端通信,他持有c++層的對(duì)象,c++層對(duì)象又持有Libuv的結(jié)構(gòu)體,Libuv結(jié)構(gòu)體又持有fd。
const socket = new Socket({
handle: clientHandle,
allowHalfOpen: self.allowHalfOpen,
pauseOnCreate: self.pauseOnConnect,
readable: true,
writable: true
});
到此這篇關(guān)于nodejs處理tcp連接的核心流程的文章就介紹到這了,更多相關(guān)nodejs處理tcp連接內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
快速刪除 node_modules 目錄的集中方法(多種方法)
本文介紹了三種快速刪除node_modules目錄的方法:使用rimraf工具、通過(guò)npx運(yùn)行rimraf以及在Windows命令提示符中使用del命令,每種方法都適合不同的操作系統(tǒng)和使用場(chǎng)景2024-11-11
小結(jié)Node.js中非阻塞IO和事件循環(huán)
本文針對(duì)在Node.js關(guān)鍵的兩個(gè)概念:非阻塞IO和事件循環(huán)進(jìn)行了適當(dāng)?shù)目偨Y(jié),需要的朋友可以參考下2014-09-09
nodejs超出最大的調(diào)用棧錯(cuò)誤問(wèn)題
這篇文章主要介紹了nodejs超出最大的調(diào)用棧錯(cuò)誤問(wèn)題,需要的朋友可以參考下2017-12-12
Nginx設(shè)置為Node.js的前端服務(wù)器方法總結(jié)
在本篇文章中小編給大家分享了關(guān)于Nginx設(shè)置為Node.js的前端服務(wù)器的方法和實(shí)例,需要的朋友們學(xué)習(xí)下。2019-03-03
簡(jiǎn)單談?wù)刵ode.js 版本控制 nvm和 n
大量開(kāi)發(fā)者的貢獻(xiàn)使Node版本的迭代速度很快,版本很多(橫跨0.6到0.11),所以升級(jí)Node版本就成為了一個(gè)問(wèn)題。目前有n和nvm這兩個(gè)工具可以對(duì)Node進(jìn)行無(wú)痛升級(jí),本文簡(jiǎn)單介紹一下二者的使用。2015-10-10
nodejs基于mssql模塊連接sqlserver數(shù)據(jù)庫(kù)的簡(jiǎn)單封裝操作示例
這篇文章主要介紹了nodejs基于mssql模塊連接sqlserver數(shù)據(jù)庫(kù)的簡(jiǎn)單封裝操作,結(jié)合實(shí)例形式分析了nodejs中mssql模塊的安裝與操作sqlserver數(shù)據(jù)庫(kù)相關(guān)使用技巧,需要的朋友可以參考下2018-01-01

