libuv之unix域的使用

之前分析了unix域在libuv的基本原理。今天以一個簡單的例子看一下如何使用它。本文涉及到一些網絡編程的知識,不過文章不打算講解這些,如果不瞭解可以先了解一下,或者留言。

void remove_sock(int sig) {
    uv_fs_t req;
    // 刪除unix域對應的路徑
    uv_fs_unlink(loop, &req, PIPENAME, NULL);
    // 退出進程
    exit(0);
}
int main() {
    loop = uv_default_loop();

    uv_pipe_t server;
    uv_pipe_init(loop, &server, 0);
    // 註冊SIGINT信號的信號處理函數是remove_sock
    signal(SIGINT, remove_sock);

    int r;
    // 綁定unix路徑到socket
    if ((r = uv_pipe_bind(&server, PIPENAME))) {
        fprintf(stderr, "Bind error %s\n", uv_err_name(r));
        return 1;
    }
    /*
        把unix域對應的文件文件描述符設置爲listen狀態。
        開啓監聽請求的到來,連接的最大個數是128。有連接時的回調是on_new_connection
    */
    if ((r = uv_listen((uv_stream_t*) &server, 128, on_new_connection))) {
        fprintf(stderr, "Listen error %s\n", uv_err_name(r));
        return 2;
    }
    // 啓動事件循環
    return uv_run(loop, UV_RUN_DEFAULT);
}

對於看過之前的文章或者瞭解網絡編程的同學來說。上面的代碼看起來會比較簡單。所以就不具體分析。他執行完後就是啓動了一個服務。同主機的進程可以訪問(連接)他。之前說過unix域的實現和tcp的實現類型。都是基於連接的模式。服務器啓動等待連接,客戶端去連接。然後服務器逐個摘下連接的節點進行處理。我們從處理連接的函數on_new_connection開始分析整個流程。

// 有連接到來時的回調
void on_new_connection(uv_stream_t *server, int status) {
    // 有連接到來,申請一個結構體表示他
    uv_pipe_t *client = (uv_pipe_t*) malloc(sizeof(uv_pipe_t));
    uv_pipe_init(loop, client, 0);
    // 把accept返回的fd記錄到client,client是用於和客戶端通信的結構體
    if (uv_accept(server, (uv_stream_t*) client) == 0) {
        /*
            註冊讀事件,等待客戶端發送信息過來,
            alloc_buffer分配內存保存客戶端的發送過來的信息,
            echo_read是回調
        */
        uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
    }
    else {
        uv_close((uv_handle_t*) client, NULL);
    }
}

分析on_new_connection之前,我們先看一下該函數的執行時機。該函數是在uv__server_io函數中被執行,而uv__server_io是在監聽的socket(即listen的那個)有可讀事件時觸發的回調。我們看看uv__server_io的部分邏輯。

// 有連接到來,進行accept
    err = uv__accept(uv__stream_fd(stream));
    // 保存通信socket對應的文件描述符
    stream->accepted_fd = err;
    /*
	    有連接,執行上層回調,connection_cb一般會調用uv_accept消費accepted_fd。
	    然後重新註冊等待可讀事件
    */
    stream->connection_cb(stream, 0);

當有連接到來時,服務器調用uv__accept摘取一個連接節點(實現上,操作系統會返回一個文件描述符,作用類似一個id)。然後把文件描述符保存到accepted_fd字段,接着執行connection_cb回調。就是我們設置的on_new_connection。

uv__stream_fd(stream)是我們啓動的服務器對應的文件描述符。stream就是表示服務器的結構體。在unix域裏,他實際上是一個uv_pipe_s結構體。uv_stream_s是uv_pipe_s的父類。類似c++的繼承。

我們回頭看一下on_new_connection的代碼。主要邏輯如下。
1 申請一個uv_pipe_t結構體用於保存和客戶端通信的信息。
2 執行uv_accept
3 執行uv_read_start開始等待數據的到來,然後讀取數據。
我們分析一下2和3。我們看一下uv_accept的主要邏輯。

switch (client->type) {
    case UV_NAMED_PIPE:
      // 設置流的標記,保存文件描述符到流上
      uv__stream_open(
	      client,server->accepted_fd,
	      UV_HANDLE_READABLE | UV_HANDLE_WRITABLE
      );
}

uv_accept中把剛纔accept到的文件描述符保存到client中。這樣我們後續就可以通過client和客戶端通信。至於uv_read_start,之前在stream的文章中已經分析過。就不再深入分析。我們主要分析echo_read。echo_read在客戶端給服務器發送信息時被觸發。

void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) {
    // 有數據,則回寫
    if (nread > 0) {
        write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t));
        // 指向客戶端發送過來的數據
        req->buf = uv_buf_init(buf->base, nread);
        // 回寫給客戶端,echo_write是寫成功後的回調
        uv_write((uv_write_t*) req, client, &req->buf, 1, echo_write);
        return;
    }
    // 沒有數據了,關閉
    if (nread < 0) {
        if (nread != UV_EOF)
            fprintf(stderr, "Read error %s\n", uv_err_name(nread));
        // 銷燬和客戶端通信的結構體,即關閉通信
        uv_close((uv_handle_t*) client, NULL);
    }

    free(buf->base);
}

沒有數據的時候,直接銷燬和客戶端通信的結構體和撤銷結構體對應的讀寫事件。我們主要分析有數據時的處理邏輯。當有數據到來時,服務器調用uv_write對數據進行回寫。我們看到uv_write的第二個參數是client。即往client對應的文件描述符中寫數據。也就是往客戶端寫。uv_write的邏輯在stream中已經分析過,所以也不打算深入分析。主要邏輯就是在client對應的stream上寫入數據,緩存起來,然後等待可寫時,再寫到對端。寫完成後執行echo_write釋放數據佔據的內存。這就是使用unix域通信的整個過程。unix域還有一個複雜的應用是涉及到傳遞文件描述符。即uv_pipe_s的ipc字段。這個後續再開一篇文章分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章