nodejs處理tcp連接的核心流程
前幾天和一個小伙伴交流了一下nodejs中epoll和處理請求的一些知識,今天簡單來聊一下nodejs處理請求的邏輯。我們從listen函數(shù)開始。
int uv_tcp_listen(uv_tcp_t* tcp, int backlog, uv_connection_cb cb) { // 設(shè)置處理的請求的策略,見下面的分析 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); // 開始監(jiān)聽 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)聽到連接到來時執(zhí)行 tcp->io_watcher.cb = uv__server_io; // 插入觀察者隊(duì)列,這時候還沒有增加到epoll,poll io階段再遍歷觀察者隊(duì)列進(jìn)行處理(epoll_ctl) uv__io_start(tcp->loop, &tcp->io_watcher, POLLIN); return 0; }
我們看到,當(dāng)我們createServer的時候,到Libuv層就是傳統(tǒng)的網(wǎng)絡(luò)編程的邏輯。這時候我們的服務(wù)就啟動了。在poll io階段,我們的監(jiān)聽型的文件描述符和上下文(感興趣的事件、回調(diào)等)就會注冊到epoll中。正常來說就阻塞在epoll。那么這時候有一個tcp連接到來,會怎樣呢?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ù)器對應(yīng)的fd while (uv__stream_fd(stream) != -1) { // 通過accept拿到和客戶端通信的fd,我們看到這個fd和服務(wù)器的fd是不一樣的 err = uv__accept(uv__stream_fd(stream)); // uv__stream_fd(stream)對應(yīng)的fd是非阻塞的,返回這個錯說明沒有連接可用accept了,直接返回 if (err < 0) { if (err == UV_EAGAIN || err == UV__ERR(EWOULDBLOCK)) return; } // 記錄下來 stream->accepted_fd = err; // 執(zhí)行回調(diào) stream->connection_cb(stream, 0); /* stream->accepted_fd為-1說明在回調(diào)connection_cb里已經(jīng)消費(fèi)了accepted_fd, 否則先注銷服務(wù)器在epoll中的fd的讀事件,等待消費(fèi)后再注冊,即不再處理請求了 */ 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,表示每次只處理一個連接,然后 睡眠一會,給機(jī)會給其他進(jìn)程accept(多進(jìn)程架構(gòu)時)。如果不是多進(jìn)程架構(gòu),又設(shè)置這個, 就會導(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在一個循環(huán)中不斷accept新的fd,然后執(zhí)行回調(diào),正常來說,回調(diào)會消費(fèi)fd,如此循環(huán),直到?jīng)]有連接可處理了。接下來,我們重點(diǎn)看看回調(diào)里是如何消費(fèi)fd的,大量的循環(huán)會不會消耗過多時間導(dǎo)致Libuv的事件循環(huán)被阻塞一會。tcp的回調(diào)是c++層的OnConnection。
// 有連接時觸發(fā)的回調(diào) template <typename WrapType, typename UVType> void ConnectionWrap<WrapType, UVType>::OnConnection(uv_stream_t* handle, int status) { // 拿到Libuv結(jié)構(gòu)體對應(yīng)的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所對應(yīng)的c++層對象存到wrap中 ASSIGN_OR_RETURN_UNWRAP(&wrap, client_obj); // 拿到對應(yīng)的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()); } // 回調(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); }
代碼看起來很復(fù)雜,我們只需要關(guān)注uv_accept。uv_accept的參數(shù),第一個是服務(wù)器對應(yīng)的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設(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主要就是兩個邏輯,把和客戶端通信的fd設(shè)置到client中,并標(biāo)記已經(jīng)消費(fèi),從而驅(qū)動剛才講的while循環(huán)繼續(xù)執(zhí)行。對于上層來說,就是拿到了一個和客戶端的對象,在Libuv層是結(jié)構(gòu)體,在c++層是一個c++對象,在js層是一個js對象,他們?nèi)齻€是一層層封裝且關(guān)聯(lián)起來的,最核心的是Libuv的client結(jié)構(gòu)體中的fd,這是和客戶端通信的底層門票。最后回調(diào)js層,那就是執(zhí)行net.js的onconnection。onconnection又封裝了一個Socket對象用于表示和客戶端通信,他持有c++層的對象,c++層對象又持有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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Nodejs做文本數(shù)據(jù)處理實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Nodejs做文本數(shù)據(jù)處理實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Node.js中path.resolve與path.join的區(qū)別與作用詳解
path.resolve和path.join都是屬于path核心模塊下的方法,用來拼接路徑,下面這篇文章主要給大家介紹了關(guān)于Node.js中path.resolve與path.join的區(qū)別與作用的相關(guān)資料,需要的朋友可以參考下2023-03-03在nodejs中創(chuàng)建child process的方法
這篇文章主要介紹了在nodejs中創(chuàng)建child process的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01如何在 Node.js 中使用 axios 配置代理并實(shí)現(xiàn)圖片并發(fā)下載
這篇文章主要介紹了如何在Node.js中使用axios配置代理并實(shí)現(xiàn)圖片并發(fā)下載,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-07-07node.js連接MongoDB數(shù)據(jù)庫的2種方法教程
這幾天一直在學(xué)習(xí)mongdb的基礎(chǔ)知識,跟著網(wǎng)上大神的腳步(代碼)去模擬連接mongodb數(shù)據(jù)庫,下面這篇文章就給大家總結(jié)介紹了node.js連接MongoDB數(shù)據(jù)庫的2種方法教程,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-05-05