簡單總結nodejs處理tcp連線的核心流程
導讀 | 這篇文章主要介紹了nodejs處理tcp連線的核心流程,本文透過例項程式碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑑價值,需要的朋友可以參考下 |
前幾天和一個小夥伴交流了一下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。
// 有連線時觸發的回撥 templatevoid ConnectionWrap::OnConnection(uv_stream_t* handle, int status) { // 拿到Libuv結構體對應的c++層物件 WrapType* wrap_data = static_cast(handle->data); CHECK_EQ(&wrap_data->handle_, reinterpret_cast(handle)); Environment* env = wrap_data->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); // 和客戶端通訊的物件 Localclient_handle; if (status == 0) { // Instantiate the client javascript object and handle. // 新建一個js層使用物件 Local 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(&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 Localargv[] = { 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 }); const socket = new Socket({ handle: clientHandle, allowHalfOpen: self.allowHalfOpen, pauseOnCreate: self.pauseOnConnect, readable: true, writable: true });
到此這篇關於nodejs處理tcp連線的核心流程的文章就介紹到這了,感謝大家的支援。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2762452/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- TCP連線的坑總結TCP
- 核心連結的簡單使用
- nodejs 連線 mysql 查詢事務處理NodeJSMySql
- 淺析TCP和nodejs中TCP的簡單應用TCPNodeJS
- 深入理解nodejs的HTTP處理流程NodeJSHTTP
- 中文文字挖掘預處理流程總結
- 英文文字挖掘預處理流程總結
- Django執行方式及處理流程總結Django
- 單機最大tcp連線數TCP
- ? 抓包分析 TCP 建立和斷開連線的流程TCP
- 線性表中的單向連結串列的簡單操作
- 簡單的字串處理字串
- 簡單的文字處理
- 串的簡單處理
- 單機最大的TCP連線數及其修改TCP
- 內連線、外連線總結
- 分散式 | 資料庫連線如何正確處理 TCP 連線三次握手失敗分散式資料庫TCP
- 使用tcpdump,netstat簡單看下tcp連線握手與揮手的過程和核心緩衝區的變化TCP
- tcp 連線TCP
- TCP的連線建立TCP
- GPU 渲染管線簡單總結(網上資料總結)GPU
- 處理方塊之間的連線線
- 行連線的處理方式指引
- 單臺伺服器最大tcp連線伺服器TCP
- 流程圖——正交連線的演算法的一種簡單實現流程圖演算法
- 限制單個IP併發TCP連線的方法TCP
- C++異常處理機制核心觀點總結C++
- Pygame的簡單總結GAM
- jQuery的動畫處理總結jQuery動畫
- Jdbc引入連線池,JdbcTemplate處理結果集的優化JDBC優化
- TCP 連線管理TCP
- 簡單的php連線mysql類PHPMySql
- ESP作為單連線中的TCP客戶端TCP客戶端
- 限制單個IP併發TCP連線的方法(轉)TCP
- ORACLE的簡單處理高水位Oracle
- spring 簡單的使用 Hikari連線池 和 jdbc連線mysql 的一個簡單例子SpringJDBCMySql單例
- mysql 外連線總結MySql
- Oracle 左右連線總結Oracle