<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                #Processes libuv提供了相當多的子進程管理函數,并且是跨平臺的,還允許使用stream,或者說pipe完成進程間通信。 在UNIX中有一個共識,就是進程只做一件事,并把它做好。因此,進程通常通過創建子進程來完成不同的任務(例如,在shell中使用pipe)。 一個多進程的,通過消息通信的模型,總比多線程的,共享內存的模型要容易理解得多。 當前一個比較常見的反對事件驅動編程的原因在于,其不能很好地利用現代多核計算機的優勢。一個多線程的程序,內核可以將線程調度到不同的cpu核心中執行,以提高性能。但是一個event-loop的程序只有一個線程。實際上,工作區可以被分配到多進程上,每一個進程執行一個event-loop,然后每一個進程被分配到不同的cpu核心中執行。 ##Spawning child processes 一個最簡單的用途是,你想要開始一個進程,然后知道它什么時候終止。需要使用`uv_spawn`完成任務: ####spawn/main.c ```c uv_loop_t *loop; uv_process_t child_req; uv_process_options_t options; int main() { loop = uv_default_loop(); char* args[3]; args[0] = "mkdir"; args[1] = "test-dir"; args[2] = NULL; options.exit_cb = on_exit; options.file = "mkdir"; options.args = args; int r; if ((r = uv_spawn(loop, &child_req, &options))) { fprintf(stderr, "%s\n", uv_strerror(r)); return 1; } else { fprintf(stderr, "Launched process with ID %d\n", child_req.pid); } return uv_run(loop, UV_RUN_DEFAULT); } ``` #####Note >由于上述的options是全局變量,因此被初始化為0。如果你在局部變量中定義options,請記得將所有沒用的域設為0 ```c uv_process_options_t options = {0}; ``` `uv_process_t`只是作為句柄,所有的選擇項都通過`uv_process_options_t`設置,為了簡單地開始一個進程,你只需要設置file和args,file是要執行的程序,args是所需的參數(和c語言中main函數的傳入參數類似)。因為`uv_spawn`在內部使用了[execvp](http://man7.org/linux/man-pages/man3/exec.3.html),所以不需要提供絕對地址。遵從慣例,**實際傳入參數的數目要比需要的參數多一個,因為最后一個參數會被設為NULL**。 在函數`uv_spawn`被調用之后,`uv_process_t.pid`會包含子進程的id。 回調函數`on_exit()`會在被調用的時候,傳入exit狀態和導致exit的信號。 ####spawn/main.c ```c void on_exit(uv_process_t *req, int64_t exit_status, int term_signal) { fprintf(stderr, "Process exited with status %" PRId64 ", signal %d\n", exit_status, term_signal); uv_close((uv_handle_t*) req, NULL); ``` 在進程關閉后,需要回收handler。 ##Changing process parameters 在子進程開始執行前,你可以通過使用`uv_process_options_t`設置運行環境。 ###Change execution directory 設置`uv_process_options_t.cwd`,更改相應的目錄。 ###Set environment variables `uv_process_options_t.env`的格式是以null為結尾的字符串數組,其中每一個字符串的形式都是`VAR=VALUE`。這些值用來設置進程的環境變量。如果子進程想要繼承父進程的環境變量,就將`uv_process_options_t.env`設為null。 ###Option flags 通過使用下面標識的按位或的值設置`uv_process_options_t.flags`的值,可以定義子進程的行為: >* `UV_PROCESS_SETUID`-將子進程的執行用戶id(UID)設置為`uv_process_options_t.uid`中的值。 * `UV_PROCESS_SETGID`-將子進程的執行組id(GID)設置為`uv_process_options_t.gid`中的值。 只有在unix系的操作系統中支持設置用戶id和組id,在windows下設置會失敗,`uv_spawn`會返回`UV_ENOTSUP`。 * `UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS`-在windows上,`uv_process_options_t.args`參數不要用引號包裹。此標記對unix無效。 * `UV_PROCESS_DETACHED`-在新會話(session)中啟動子進程,這樣子進程就可以在父進程退出后繼續進行。請看下面的例子: ##Detaching processes 使用標識`UV_PROCESS_DETACHED`可以啟動守護進程(daemon),或者是使得子進程從父進程中獨立出來,這樣父進程的退出就不會影響到它。 ####detach/main.c ```c int main() { loop = uv_default_loop(); char* args[3]; args[0] = "sleep"; args[1] = "100"; args[2] = NULL; options.exit_cb = NULL; options.file = "sleep"; options.args = args; options.flags = UV_PROCESS_DETACHED; int r; if ((r = uv_spawn(loop, &child_req, &options))) { fprintf(stderr, "%s\n", uv_strerror(r)); return 1; } fprintf(stderr, "Launched sleep with PID %d\n", child_req.pid); uv_unref((uv_handle_t*) &child_req); return uv_run(loop, UV_RUN_DEFAULT); ``` 記住一點,就是handle會始終監視著子進程,所以你的程序不會退出。`uv_unref()`會解除handle。 ##Sending signals to processes libuv打包了unix標準的`kill(2)`系統調用,并且在windows上實現了一個類似用法的調用,但要注意:所有的`SIGTERM`,`SIGINT`和`SIGKILL`都會導致進程的中斷。`uv_kill`函數如下所示: ```c uv_err_t uv_kill(int pid, int signum); ``` 對于用libuv啟動的進程,應該使用`uv_process_kill`終止,它會以`uv_process_t`作為第一個參數,而不是pid。當使用`uv_process_kill`后,記得使用`uv_close`關閉`uv_process_t`。 ##Signals libuv對unix信號和一些[windows下類似的機制](http://docs.libuv.org/en/v1.x/signal.html#signal),做了很好的打包。 使用`uv_signal_init`初始化handle(`uv_signal_t `),然后將它與loop關聯。為了使用handle監聽特定的信號,使用`uv_signal_start()`函數。每一個handle只能與一個信號關聯,后續的`uv_signal_start`會覆蓋前面的關聯。使用`uv_signal_stop`終止監聽。下面的這個小例子展示了各種用法: ####signal/main.c ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <uv.h> uv_loop_t* create_loop() { uv_loop_t *loop = malloc(sizeof(uv_loop_t)); if (loop) { uv_loop_init(loop); } return loop; } void signal_handler(uv_signal_t *handle, int signum) { printf("Signal received: %d\n", signum); uv_signal_stop(handle); } // two signal handlers in one loop void thread1_worker(void *userp) { uv_loop_t *loop1 = create_loop(); uv_signal_t sig1a, sig1b; uv_signal_init(loop1, &sig1a); uv_signal_start(&sig1a, signal_handler, SIGUSR1); uv_signal_init(loop1, &sig1b); uv_signal_start(&sig1b, signal_handler, SIGUSR1); uv_run(loop1, UV_RUN_DEFAULT); } // two signal handlers, each in its own loop void thread2_worker(void *userp) { uv_loop_t *loop2 = create_loop(); uv_loop_t *loop3 = create_loop(); uv_signal_t sig2; uv_signal_init(loop2, &sig2); uv_signal_start(&sig2, signal_handler, SIGUSR1); uv_signal_t sig3; uv_signal_init(loop3, &sig3); uv_signal_start(&sig3, signal_handler, SIGUSR1); while (uv_run(loop2, UV_RUN_NOWAIT) || uv_run(loop3, UV_RUN_NOWAIT)) { } } int main() { printf("PID %d\n", getpid()); uv_thread_t thread1, thread2; uv_thread_create(&thread1, thread1_worker, 0); uv_thread_create(&thread2, thread2_worker, 0); uv_thread_join(&thread1); uv_thread_join(&thread2); return 0; } ``` #####Note >`uv_run(loop, UV_RUN_NOWAIT)`和`uv_run(loop, UV_RUN_ONCE)`非常像,因為它們都只處理一個事件。但是不同在于,UV_RUN_ONCE會在沒有任務的時候阻塞,但是UV_RUN_NOWAIT會立刻返回。我們使用`NOWAIT`,這樣才使得一個loop不會因為另外一個loop沒有要處理的事件而挨餓。 當向進程發送`SIGUSR1`,你會發現signal_handler函數被激發了4次,每次都對應一個`uv_signal_t`。然后signal_handler調用uv_signal_stop終止了每一個`uv_signal_t`,最終程序退出。對每個handler函數來說,任務的分配很重要。一個使用了多個event-loop的服務器程序,只要簡單地給每一個進程添加信號SIGINT監視器,就可以保證程序在中斷退出前,數據能夠安全地保存。 ##Child Process I/O 一個正常的新產生的進程都有自己的一套文件描述符映射表,例如0,1,2分別對應`stdin`,`stdout`和`stderr`。有時候父進程想要將自己的文件描述符映射表分享給子進程。例如,你的程序啟動了一個子命令,并且把所有的錯誤信息輸出到log文件中,但是不能使用`stdout`。因此,你想要使得你的子進程和父進程一樣,擁有`stderr`。在這種情形下,libuv提供了繼承文件描述符的功能。在下面的例子中,我們會調用這么一個測試程序: ####proc-streams/test.c ```c #include <stdio.h> int main() { fprintf(stderr, "This is stderr\n"); printf("This is stdout\n"); return 0; } ``` 實際的執行程序` proc-streams`在運行的時候,只向子進程分享`stderr`。使用`uv_process_options_t`的`stdio`域設置子進程的文件描述符。首先設置`stdio_count`,定義文件描述符的個數。`uv_process_options_t.stdio`是一個`uv_stdio_container_t`數組。定義如下: ```c typedef struct uv_stdio_container_s { uv_stdio_flags flags; union { uv_stream_t* stream; int fd; } data; } uv_stdio_container_t; ``` 上邊的flag值可取多種。比如,如果你不打算使用,可以設置為`UV_IGNORE`。如果與stdio中對應的前三個文件描述符被標記為`UV_IGNORE`,那么它們會被重定向到`/dev/null`。 因為我們想要傳遞一個已經存在的文件描述符,所以使用`UV_INHERIT_FD`。因此,fd被設為stderr。 ####proc-streams/main.c ```c int main() { loop = uv_default_loop(); /* ... */ options.stdio_count = 3; uv_stdio_container_t child_stdio[3]; child_stdio[0].flags = UV_IGNORE; child_stdio[1].flags = UV_IGNORE; child_stdio[2].flags = UV_INHERIT_FD; child_stdio[2].data.fd = 2; options.stdio = child_stdio; options.exit_cb = on_exit; options.file = args[0]; options.args = args; int r; if ((r = uv_spawn(loop, &child_req, &options))) { fprintf(stderr, "%s\n", uv_strerror(r)); return 1; } return uv_run(loop, UV_RUN_DEFAULT); } ``` 這時你啟動proc-streams,也就是在main中產生一個執行test的子進程,你只會看到“This is stderr”。你可以試著設置stdout也繼承父進程。 同樣可以把上述方法用于流的重定向。比如,把flag設為`UV_INHERIT_STREAM`,然后再設置父進程中的`data.stream`,這時子進程只會把這個stream當成是標準的I/O。這可以用來實現,例如[CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface)。 一個簡單的CGI腳本的例子如下: ####cgi/tick.c ```c #include <stdio.h> #include <unistd.h> int main() { int i; for (i = 0; i < 10; i++) { printf("tick\n"); fflush(stdout); sleep(1); } printf("BOOM!\n"); return 0; } ``` CGI服務器用到了這章和[網絡](http://luohaha.github.io/Chinese-uvbook/source/networking.html)那章的知識,所以每一個client在中斷連接后,都會被發送10個tick。 ####cgi/main.c ```c void on_new_connection(uv_stream_t *server, int status) { if (status == -1) { // error! return; } uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t)); uv_tcp_init(loop, client); if (uv_accept(server, (uv_stream_t*) client) == 0) { invoke_cgi_script(client); } else { uv_close((uv_handle_t*) client, NULL); } ``` 上述代碼中,我們接受了連接,并把socket(流)傳遞給`invoke_cgi_script`。 ####cgi/main.c ```c args[1] = NULL; /* ... finding the executable path and setting up arguments ... */ options.stdio_count = 3; uv_stdio_container_t child_stdio[3]; child_stdio[0].flags = UV_IGNORE; child_stdio[1].flags = UV_INHERIT_STREAM; child_stdio[1].data.stream = (uv_stream_t*) client; child_stdio[2].flags = UV_IGNORE; options.stdio = child_stdio; options.exit_cb = cleanup_handles; options.file = args[0]; options.args = args; // Set this so we can close the socket after the child process exits. child_req.data = (void*) client; int r; if ((r = uv_spawn(loop, &child_req, &options))) { fprintf(stderr, "%s\n", uv_strerror(r)); ``` cgi的`stdout`被綁定到socket上,所以無論tick腳本程序打印什么,都會發送到client端。通過使用進程,我們能夠很好地處理讀寫并發操作,而且用起來也很方便。但是要記得這么做,是很浪費資源的。 ##Pipes libuv的`uv_pipe_t`結構可能會讓一些unix程序員產生困惑,因為它像魔術般變幻出`|`和[`pipe(7)`](http://man7.org/linux/man-pages/man7/pipe.7.html)。但這里的`uv_pipe_t`并不是IPC機制里的匿名管道(在IPC里,pipe是匿名管道,只允許父子進程之間通信。FIFO則允許沒有親戚關系的進程間通信,顯然llibuv里的`uv_pipe_t`不是第一種)。`uv_pipe_t`背后有[unix本地socket](http://man7.org/linux/man-pages/man7/unix.7.html)或者[windows實名管道](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365590.aspx)的支持,可以實現多進程間的通信。下面會具體討論。 ####Parent-child IPC 父進程與子進程可以通過單工或者雙工管道通信,獲得管道可以通過設置`uv_stdio_container_t.flags`為`UV_CREATE_PIPE`,`UV_READABLE_PIPE`或者`UV_WRITABLE_PIPE`的按位或的值。上述的讀/寫標記是對于子進程而言的。 ####Arbitrary process IPC 既然本地socket具有確定的名稱,而且是以文件系統上的位置來標示的(例如,unix中socket是文件的一種存在形式),那么它就可以用來在不相關的進程間完成通信任務。被開源桌面環境使用的[`D-BUS`系統](http://www.freedesktop.org/wiki/Software/dbus/)也是使用了本地socket來作為事件通知的,例如,當消息來到,或者檢測到硬件的時候,各種應用程序會被通知到。mysql服務器也運行著一個本地socket,等待客戶端的訪問。 當使用本地socket的時候,客戶端/服務器模型通常和之前類似。在完成初始化后,發送和接受消息的方法和之前的tcp類似,接下來我們同樣適用echo服務器的例子來說明。 ####pipe-echo-server/main.c ```c int main() { loop = uv_default_loop(); uv_pipe_t server; uv_pipe_init(loop, &server, 0); signal(SIGINT, remove_sock); int r; if ((r = uv_pipe_bind(&server, "echo.sock"))) { fprintf(stderr, "Bind error %s\n", uv_err_name(r)); return 1; } 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); } ``` 我們把socket命名為echo.sock,意味著它將會在本地文件夾中被創造。對于stream API來說,本地socekt表現得和tcp的socket差不多。你可以使用[socat](http://www.dest-unreach.org/socat/)測試一下服務器: ``` $ socat - /path/to/socket ``` 客戶端如果想要和服務器端連接的話,應該使用: ```c void uv_pipe_connect(uv_connect_t *req, uv_pipe_t *handle, const char *name, uv_connect_cb cb); ``` 上述函數,name應該為echo.sock。 ####Sending file descriptors over pipes 最酷的事情是本地socket可以傳遞文件描述符,也就是說進程間可以交換文件描述符。這樣就允許進程將它們的I/O傳遞給其他進程。它的應用場景包括,負載均衡服務器,分派工作進程等,各種可以使得cpu使用最優化的應用。libuv當前只支持通過管道傳輸**TCP sockets或者其他的pipes**。 為了展示這個功能,我們將來實現一個由循環中的工人進程處理client端請求,的這么一個echo服務器程序。這個程序有一些復雜,在教程中只截取了部分的片段,為了更好地理解,我推薦你去讀下完整的[代碼](https://github.com/nikhilm/uvbook/tree/master/code/multi-echo-server)。 工人進程很簡單,文件描述符將從主進程傳遞給它。 ####multi-echo-server/worker.c ```c uv_loop_t *loop; uv_pipe_t queue; int main() { loop = uv_default_loop(); uv_pipe_init(loop, &queue, 1 /* ipc */); uv_pipe_open(&queue, 0); uv_read_start((uv_stream_t*)&queue, alloc_buffer, on_new_connection); return uv_run(loop, UV_RUN_DEFAULT); } ``` `queue`是另一端連接上主進程的管道,因此,文件描述符可以傳送過來。在`uv_pipe_init`中將`ipc`參數設置為1很關鍵,因為它標明了這個管道將被用來做進程間通信。因為主進程需要把文件handle賦給了工人進程作為標準輸入,因此我們使用`uv_pipe_open`把stdin作為pipe(別忘了,0代表stdin)。 ####multi-echo-server/worker.c ```c void on_new_connection(uv_stream_t *q, ssize_t nread, const uv_buf_t *buf) { if (nread < 0) { if (nread != UV_EOF) fprintf(stderr, "Read error %s\n", uv_err_name(nread)); uv_close((uv_handle_t*) q, NULL); return; } uv_pipe_t *pipe = (uv_pipe_t*) q; if (!uv_pipe_pending_count(pipe)) { fprintf(stderr, "No pending count\n"); return; } uv_handle_type pending = uv_pipe_pending_type(pipe); assert(pending == UV_TCP); uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t)); uv_tcp_init(loop, client); if (uv_accept(q, (uv_stream_t*) client) == 0) { uv_os_fd_t fd; uv_fileno((const uv_handle_t*) client, &fd); fprintf(stderr, "Worker %d: Accepted fd %d\n", getpid(), fd); uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read); } else { uv_close((uv_handle_t*) client, NULL); } } ``` 首先,我們調用`uv_pipe_pending_count`來確定從handle中可以讀取出數據。如果你的程序能夠處理不同類型的handle,這時`uv_pipe_pending_type`就可以用來決定當前的類型。雖然在這里使用`accept`看起來很怪,但實際上是講得通的。`accept`最常見的用途是從其他的文件描述符(監聽的socket)獲取文件描述符(client端)。這從原理上說,和我們現在要做的是一樣的:從queue中獲取文件描述符(client)。接下來,worker可以執行標準的echo服務器的工作了。 我們再來看看主進程,觀察如何啟動worker來達到負載均衡。 ####multi-echo-server/main.c ```c struct child_worker { uv_process_t req; uv_process_options_t options; uv_pipe_t pipe; } *workers; ``` `child_worker`結構包裹著進程,和連接主進程和各個獨立進程的管道。 ####multi-echo-server/main.c ```c void setup_workers() { round_robin_counter = 0; // ... // launch same number of workers as number of CPUs uv_cpu_info_t *info; int cpu_count; uv_cpu_info(&info, &cpu_count); uv_free_cpu_info(info, cpu_count); child_worker_count = cpu_count; workers = calloc(sizeof(struct child_worker), cpu_count); while (cpu_count--) { struct child_worker *worker = &workers[cpu_count]; uv_pipe_init(loop, &worker->pipe, 1); uv_stdio_container_t child_stdio[3]; child_stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; child_stdio[0].data.stream = (uv_stream_t*) &worker->pipe; child_stdio[1].flags = UV_IGNORE; child_stdio[2].flags = UV_INHERIT_FD; child_stdio[2].data.fd = 2; worker->options.stdio = child_stdio; worker->options.stdio_count = 3; worker->options.exit_cb = close_process_handle; worker->options.file = args[0]; worker->options.args = args; uv_spawn(loop, &worker->req, &worker->options); fprintf(stderr, "Started worker %d\n", worker->req.pid); } } ``` 首先,我們使用酷炫的`uv_cpu_info`函數獲取到當前的cpu的核心個數,所以我們也能啟動一樣數目的worker進程。再次強調一下,務必將`uv_pipe_init`的ipc參數設置為1。接下來,我們指定子進程的`stdin`是一個可讀的管道(從子進程的角度來說)。接下來的一切就很直觀了,worker進程被啟動,等待著文件描述符被寫入到他們的標準輸入中。 在主進程的`on_new_connection`中,我們接收了client端的socket,然后把它傳遞給worker環中的下一個可用的worker進程。 ####multi-echo-server/main.c ```c void on_new_connection(uv_stream_t *server, int status) { if (status == -1) { // error! return; } uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t)); uv_tcp_init(loop, client); if (uv_accept(server, (uv_stream_t*) client) == 0) { uv_write_t *write_req = (uv_write_t*) malloc(sizeof(uv_write_t)); dummy_buf = uv_buf_init("a", 1); struct child_worker *worker = &workers[round_robin_counter]; uv_write2(write_req, (uv_stream_t*) &worker->pipe, &dummy_buf, 1, (uv_stream_t*) client, NULL); round_robin_counter = (round_robin_counter + 1) % child_worker_count; } else { uv_close((uv_handle_t*) client, NULL); } } ``` `uv_write2`能夠在所有的情形上做了一個很好的抽象,我們只需要將client作為一個參數即可完成傳輸。現在,我們的多進程echo服務器已經可以運轉起來啦。 感謝Kyle指出了`uv_write2`需要一個不為空的buffer。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看