今天就跟大家聊聊有關使用nodejs怎么對tcp連接進行處理,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
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
});看完上述內容,你們對使用nodejs怎么對tcp連接進行處理有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。