精华内容
下载资源
问答
  • Libuv中文文档网络接口
    千次阅读
    2016-08-31 16:35:59

    Libuv中文文档网络接口

    文章目录

    libuv 的网络接口与 BSD 套接字接口存在很大的不同, 某些事情在 libuv 下变得更简单了, 并且所有接口都是都是非阻塞的, 但是原则上还是一致的. 另外 libuv 也提供了一些工具类的函数抽象了一些让人生厌的, 重复而底层的任务,比如使用 BSD 套接字结构来建立套接字, DNS 查询, 或者其他各种参数的设置.

    libuv 中在网络 I/O 中使用了 uv_tcp_tuv_udp_t 两个结构体.

    TCP

    TCP 是一种面向连接的流式协议, 因此是基于 libuv 的流式基础架构上的.

    服务器(Server)

    服务器端的 sockets 处理流程如下:

    1. uv_tcp_init 初始化 TCP 监视器.
    2. uv_tcp_bind 绑定.
    3. 在指定的监视器上调用 uv_listen 来设置回调函数, 当有新的客户端连接到来时, libuv 就会调用设置的回调函数.
    4. uv_accept 接受连接.
    5. 使用 stream operations 与客户端进行通信.

    以下是一个简单的 echo 服务器的例子:

    tcp-echo-server/main.c – The listen socket

    int main() {
        loop = uv_default_loop();
    
        uv_tcp_t server;
        uv_tcp_init(loop, &server);
    
        struct sockaddr_in bind_addr = uv_ip4_addr("0.0.0.0", 7000);
        uv_tcp_bind(&server, bind_addr);
        int r = uv_listen((uv_stream_t*) &server, 128, on_new_connection);
        if (r) {
            fprintf(stderr, "Listen error %s\n", uv_err_name(uv_last_error(loop)));
            return 1;
        }
        return uv_run(loop, UV_RUN_DEFAULT);
    }
    

    你可以看到辅助函数 uv_ip4_addr 用来将人为可读的字符串类型的 IP 地址和端口号转换成 BSD 套接字 API 所需要的 struct sockaddr_in 类型的结构. 逆变换可以使用 uv_ip4_name 来完成.

    Note

    对于 IPv6 来说应该使用 uv_ip6_* 形式的函数.

    大部分的设置(setup)函数都是普通函数, 因为他们都是 计算密集型(CPU-bound), 直到调用了 uv_listen 我们才回到 libuv 中回调函数风格. uv_listen 的第二个参数 backlog 队列长度 – 即连接队列最大长度.

    当客户端发起了新的连接时, 回调函数需要为客户端套接字设置一个监视器, 并调用 uv_accept 函数将客户端套接字与新的监视器在关联一起. 在例子中我们将从流中读取数据.

    tcp-echo-server/main.c – Accepting the client

    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_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
        }
        else {
            uv_close((uv_handle_t*) client, NULL);
        }
    }
    

    剩余部分的函数与上一节流式例子中的代码相似, 你可以在例子程序中找到具体代码, 如果套接字不再使用记得调用 uv_close 关闭该套接字. 如果你不再接受连接, 你可以在 uv_listen 的回调函数中关闭套接字.

    客户端(Client)

    在服务器端你需要调用 bind/listen/accept, 而在客户端你只需要调用 uv_tcp_connect. uv_tcp_connect 使用了与 uv_listen 风格相似的回调函数 uv_connect_cb 如下:

    uv_tcp_t socket;
    uv_tcp_init(loop, &socket);
    
    uv_connect_t connect;
    
    struct sockaddr_in dest = uv_ip4_addr("127.0.0.1", 80);
    
    uv_tcp_connect(&connect, &socket, dest, on_connect);
    

    建立连接后会调用 on_connect.

    UDP

    User Datagram Protocol 提供了无连接, 不可靠网络通信协议, 因此 libuv 并不提供流式 UDP 服务, 而是通过 uv_udp_t 结构体(用于接收)和 uv_udp_send_t 结构体(用于发送)以及相关的函数给开发人员提供了非阻塞的 UDP 服务. 所以, 真正读写 UDP 的函数与普通的流式读写非常相似.为了示范如何使用 UDP, 下面提供了一个简单的例子用来从 DHCP 获取 IP 地址. – DHCP 发现.

    Note

    你应该以 root 用户运行 udp-dhcp, 因为该程序使用了端口号低于 1024 的端口.

    udp-dhcp/main.c – Setup and send UDP packets

    uv_loop_t *loop;
    uv_udp_t send_socket;
    uv_udp_t recv_socket;
    
    int main() {
        loop = uv_default_loop();
    
        uv_udp_init(loop, &recv_socket);
        struct sockaddr_in recv_addr = uv_ip4_addr("0.0.0.0", 68);
        uv_udp_bind(&recv_socket, recv_addr, 0);
        uv_udp_recv_start(&recv_socket, alloc_buffer, on_read);
    
        uv_udp_init(loop, &send_socket);
        uv_udp_bind(&send_socket, uv_ip4_addr("0.0.0.0", 0), 0);
        uv_udp_set_broadcast(&send_socket, 1);
    
        uv_udp_send_t send_req;
        uv_buf_t discover_msg = make_discover_msg(&send_req);
    
        struct sockaddr_in send_addr = uv_ip4_addr("255.255.255.255", 67);
        uv_udp_send(&send_req, &send_socket, &discover_msg, 1, send_addr, on_send);
    
        return uv_run(loop, UV_RUN_DEFAULT);
    }
    

    Note

    0.0.0.0 地址可以绑定本机所有网口. 255.255.255.255 是广播地址, 意味着网络包可以发送给子网中所有网口, 端口 0 说明操作系统可以任意指定端口进行绑定.

    首先我们在 68 号端口上设置了绑定本机所有网口的接收套接字(DHCP 客户端), 并且设置了读监视器. 然后我们利用相同的方法设置了一个用于发送消息的套接字. 并使用 uv_udp_send 在 67 号端口上(DHCP 服务器)发送 广播消息.

    设置广播标志也是 必要 的, 不然你会得到 EACCES 错误 [1]. 发送的具体消息与本书无关, 如果你对此感兴趣, 可以参考源码. 若出错, 则读写回调函数会收到 -1 状态码.

    由于 UDP 套接字并不和特定的对等方保持连接, 所以 read 回调函数中将会收到用于标识发送者的额外信息. 如果缓冲区是由你自己的分配的, 并且不够容纳接收的数据, 则“flags“ 标志位可能是 UV_UDP_PARTIAL. 在这种情况下, 操作系统会丢弃不能容纳的数据. (这也是 UDP 为你提供的特性).

    udp-dhcp/main.c – Reading packets

    void on_read(uv_udp_t *req, ssize_t nread, uv_buf_t buf, struct sockaddr *addr, unsigned flags) {
        if (nread == -1) {
            fprintf(stderr, "Read error %s\n", uv_err_name(uv_last_error(loop)));
            uv_close((uv_handle_t*) req, NULL);
            free(buf.base);
            return;
        }
    
        char sender[17] = { 0 };
        uv_ip4_name((struct sockaddr_in*) addr, sender, 16);
        fprintf(stderr, "Recv from %s\n", sender);
    
        // ... DHCP specific code
    
        free(buf.base);
        uv_udp_recv_stop(req);
    }
    

    UDP 选项(UDP Options)

    生存时间TTL(Time-to-live)

    可以通过 uv_udp_set_ttl 来设置网络数据包的生存时间(TTL).

    仅使用 IPv6 协议

    IPv6 套接字可以同时在 IPv4 和 IPv6 协议下进行通信. 如果你只想使用 IPv6 套接字, 在调用 uv_udp_bind6 [2] 时请传递 UV_UDP_IPV6ONLY 参数.

    多播(Multicast)

    套接字可以使用如下函数订阅(取消订阅)一个多播组:

    UV_EXTERN int uv_udp_set_membership(uv_udp_t* handle,
        const char* multicast_addr, const char* interface_addr,
        uv_membership membership);
    

    membership 取值可以是 UV_JOIN_GROUPUV_LEAVE_GROUP.

    多播包的本地回路是默认开启的 [3], 可以使用 uv_udp_set_multicast_loop 来开启/关闭该特性.

    多播包的生存时间可以使用 uv_udp_set_multicast_ttl 来设置.

    DNS 查询(Querying DNS)

    libuv 提供了异步解析 DNS 的功能, 用于替代 getaddrinfo [4]. 在回调函数中, 你可以在获得的 IP 地址上执行普通的套接字操作. 让我们通过一个简单的 DNS 解析的例子来看看怎么连接 Freenode 吧:

    dns/main.c

    int main() {
        loop = uv_default_loop();
    
        struct addrinfo hints;
        hints.ai_family = PF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        hints.ai_flags = 0;
    
        uv_getaddrinfo_t resolver;
        fprintf(stderr, "irc.freenode.net is... ");
        int r = uv_getaddrinfo(loop, &resolver, on_resolved, "irc.freenode.net", "6667", &hints);
    
        if (r) {
            fprintf(stderr, "getaddrinfo call error %s\n", uv_err_name(uv_last_error(loop)));
            return 1;
        }
        return uv_run(loop, UV_RUN_DEFAULT);
    }
    

    如果 uv_getaddrinfo 返回非零, 表示在建立连接时出错, 你设置的回调函数不会被调用, 所有的参数将会在 uv_getaddrinfo 返回后被立即释放. 有关 hostname, servnamehints 结构体的文档可以在 getaddrinfo 帮助页面中找到.

    在解析回调函数中, 你可以在 struct addrinfo(s) 结构的链表中任取一个 IP. 这个例子也演示了如何使用 uv_tcp_connect. 你在回调函数中有必要调用 uv_freeaddrinfo.

    dns/main.c

    void on_resolved(uv_getaddrinfo_t *resolver, int status, struct addrinfo *res) {
        if (status == -1) {
            fprintf(stderr, "getaddrinfo callback error %s\n", uv_err_name(uv_last_error(loop)));
            return;
        }
    
        char addr[17] = {'\0'};
        uv_ip4_name((struct sockaddr_in*) res->ai_addr, addr, 16);
        fprintf(stderr, "%s\n", addr);
    
        uv_connect_t *connect_req = (uv_connect_t*) malloc(sizeof(uv_connect_t));
        uv_tcp_t *socket = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
        uv_tcp_init(loop, socket);
    
        connect_req->data = (void*) socket;
        uv_tcp_connect(connect_req, socket, *(struct sockaddr_in*) res->ai_addr, on_connect);
    
        uv_freeaddrinfo(res);
    }
    

    网络接口(Network interfaces)

    系统网络接口信息可以通过调用 uv_interface_addresses 来获得, 下面的示例程序将打印出机器上所有网络接口的细节信息, 因此你可以获知网口的哪些域的信息是可以得到的, 这在你的程序启动时绑定 IP 很方便.

    interfaces/main.c

    #include <stdio.h>
    #include <uv.h>
    
    int main() {
        char buf[512];
        uv_interface_address_t *info;
        int count, i;
    
        uv_interface_addresses(&info, &count);
        i = count;
    
        printf("Number of interfaces: %d\n", count);
        while (i--) {
            uv_interface_address_t interface = info[i];
    
            printf("Name: %s\n", interface.name);
            printf("Internal? %s\n", interface.is_internal ? "Yes" : "No");
            
            if (interface.address.address4.sin_family == AF_INET) {
                uv_ip4_name(&interface.address.address4, buf, sizeof(buf));
                printf("IPv4 address: %s\n", buf);
            }
            else if (interface.address.address4.sin_family == AF_INET6) {
                uv_ip6_name(&interface.address.address6, buf, sizeof(buf));
                printf("IPv6 address: %s\n", buf);
            }
    
            printf("\n");
        }
    
        uv_free_interface_addresses(info, count);
        return 0;
    }
    

    is_internal 对于回环接口来说为 true. 请注意如果物理网口使用了多个 IPv4/IPv6 地址, 那么它的名称将会被多次报告, 因为每个地址都会报告一次.

    更多相关内容
  • libuv-docs-chinese:libuv官方文档中文libuv文档中文翻译
  • 关于Libuv中文文档

    千次阅读 2016-08-31 16:31:48
    libuv文档

    关于Libuv中文文档

    文章目录

    libuv 是 Node 的新跨平台抽象层,用于抽象 Windows 的 IOCP 及 Unix 的 libev。作者打算在这个库的包含所有平台的差异性。

    libuv

    关于翻译

    Libuv中文文档中文文档翻译自An Introduction to libuv,为了中文表述的清晰妥当,书中很多内容并没有逐字逐句翻译,但由于中文译者的水平有限,本书中文版可能存在一些翻译错误,如果您发现了中文版的错误,请针对某个语句留言评论。

    其它版本

    如果你也想一起翻译文档,可以 fork 仓库 https://github.com/forhappy/uvbook,共同完善这份文档。

    文档目录

    序言

    • 本书为谁而写?
    • 背景
    • 本书代码

    Libuv 基础

    • 事件循环(Event loops)
    • Hello World
    • 监视器(Watchers)

    文件系统

    • 读写文件
    • 文件系统相关操作(Filesystem operations)
    • 缓冲区与流(Buffers and Streams)
    • 文件变更事件(File change events)

    网络

    • TCP
    • UDP
    • DNS 查询(Querying DNS)
    • 网络接口(Network interfaces)

    线程

    • 线程核心操作(Core thread operations)
    • 同步原语(Synchronization Primitives)
    • libuv 工作队列
    • 线程间通信(Inter-thread communication)

    进程

    • 创建子进程(Spawning child processes)
    • 改变进程参数(Changing process parameters)
    • 进程分离(Detaching processes)
    • 向进程发送信号(Sending signals to processes)
    • 信号(Signals)
    • 子进程 I/O
    • 管道(Pipes)

    多路事件循环(Multiple event loops)

    • 多路事件循环的形式(Modality)
    • 各线程拥有自己的事件循环(One loop per thread)

    工具集

    • 定时器(Timers)
    • 事件循环引用计数(Event loop reference count)
    • 空闲监视器模式(Idle watcher pattern)
    • 向工作者线程传递数据(Passing data to worker thread)
    • 轮询方式下的外部 I/O(External I/O with polling)
    • 检查并预备监视器(Check & Prepare watchers)
    • 库的加载(Loading libraries)
    • TTY

    Github地址:https://github.com/forhappy/uvbook

    展开全文
  • libuv技术文档.docx

    2019-10-16 22:40:37
    libuv中文技术文档,libuv基础入门,常用api接口调用说明,和调用流程,里面有各种例子。适合对libuv零基础的人使用
  • Libuv中文文档之进程

    千次阅读 2016-08-31 16:38:01
    libuv

    Libuv中文文档之进程

    文章目录

    libuv offers considerable child process management, abstracting the platform differences and allowing communication with the child process using streams or named pipes.

    A common idiom in Unix is for every process to do one thing and do it well. In such a case, a process often uses multiple child processes to achieve tasks (similar to using pipes in shells). A multi-process model with messages may also be easier to reason about compared to one with threads and shared memory.

    A common refrain against event-based programs is that they cannot take advantage of multiple cores in modern computers. In a multi-threaded program the kernel can perform scheduling and assign different threads to different cores, improving performance. But an event loop has only one thread. The workaround can be to launch multiple processes instead, with each process running an event loop, and each process getting assigned to a separate CPU core.

    创建子进程(Spawning child processes)

    The simplest case is when you simply want to launch a process and know when it exits. This is achieved using uv_spawn.

    spawn/main.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;
    
        if (uv_spawn(loop, &child_req, options)) {
            fprintf(stderr, "%s\n", uv_strerror(uv_last_error(loop)));
            return 1;
        }
    
        return uv_run(loop, UV_RUN_DEFAULT);
    }
    

    The uv_process_t struct only acts as the watcher, all options are set via uv_process_options_t. To simply launch a process, you need to set only the file and args fields. file is the program to execute. Since uv_spawn uses execvp internally, there is no need to supply the full path. Finally as per underlying conventions, the arguments array has to be one larger than the number of arguments, with the last element being NULL.

    After the call to uv_spawn, uv_process_t.pid will contain the process ID of the child process.

    The exit callback will be invoked with the exit status and the type of signal which caused the exit.

    spawn/main.c

    void on_exit(uv_process_t *req, int exit_status, int term_signal) {
        fprintf(stderr, "Process exited with status %d, signal %d\n", exit_status, term_signal);
        uv_close((uv_handle_t*) req, NULL);
    }
    

    It is required to close the process watcher after the process exits.

    改变进程参数(Changing process parameters)

    Before the child process is launched you can control the execution environment using fields in uv_process_options_t.

    改变执行目录(Change execution directory)

    设置 uv_process_options_t.cwd 参数改变进程的执行路径.

    设置环境变量(Set environment variables)

    uv_process_options_t.env is an array of strings, each of the form VAR=VALUE used to set up the environment variables for the process. Set this to NULL to inherit the environment from the parent (this) process.

    选项参数(Option flags)

    Setting uv_process_options_t.flags to a bitwise OR of the following flags, modifies the child process behaviour:

    • UV_PROCESS_SETUID – sets the child’s execution user ID to uv_process_options_t.uid.
    • UV_PROCESS_SETGID – sets the child’s execution group ID to uv_process_options_t.gid.

    Changing the UID/GID is only supported on Unix, uv_spawn will fail on Windows with UV_ENOTSUP.

    • UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS – No quoting or escaping of uv_process_options_t.args is done on Windows. Ignored on Unix.
    • UV_PROCESS_DETACHED – Starts the child process in a new session, which will keep running after the parent process exits. See example below.

    进程分离(Detaching processes)

    Passing the flag UV_PROCESS_DETACHED can be used to launch daemons, or child processes which are independent of the parent so that the parent exiting does not affect it.

    detach/main.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;
    
        if (uv_spawn(loop, &child_req, options)) {
            fprintf(stderr, "%s\n", uv_strerror(uv_last_error(loop)));
            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);
    }
    

    Just remember that the watcher is still monitoring the child, so your program won’t exit. Use uv_unref() if you want to be more fire-and-forget.

    向进程发送信号(Sending signals to processes)

    libuv wraps the standard kill(2) system call on Unix and implements one with similar semantics on Windows, with one caveat: all of SIGTERM, SIGINT and SIGKILL, lead to termination of the process. The signature of uv_kill is:

    uv_err_t uv_kill(int pid, int signum);
    

    For processes started using libuv, you may use uv_process_kill instead, which accepts the uv_process_t watcher as the first argument, rather than the pid. In this case, remember to call uv_close on the watcher.

    信号(Signals)

    TODO: update based on https://github.com/joyent/libuv/issues/668

    libuv provides wrappers around Unix signals with some Windows support as well.

    To make signals ‘play nice’ with libuv, the API will deliver signals to all handlers on all running event loops! Use uv_signal_init() to initialize a handler and associate it with a loop. To listen for particular signals on that handler, use uv_signal_start() with the handler function. Each handler can only be associated with one signal number, with subsequent calls to uv_signal_start() overwriting earlier associations. Use uv_signal_stop() to stop watching. Here is a small example demonstrating the various possibilities:

    signal/main.c

    #include <stdio.h>
    #include <unistd.h>
    #include <uv.h>
    
    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 = uv_loop_new();
    
        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 = uv_loop_new();
        uv_loop_t *loop3 = uv_loop_new();
    
        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;
    }
    

    Send SIGUSR1 to the process, and you’ll find the handler being invoked 4 times, one for each uv_signal_t. The handler just stops each handle, so that the program exits. This sort of dispatch to all handlers is very useful. A server using multiple event loops could ensure that all data was safely saved before termination, simply by every loop adding a watcher for SIGINT.

    子进程 I/O

    A normal, newly spawned process has its own set of file descriptors, with 0, 1 and 2 being stdin, stdout and stderr respectively. Sometimes you may want to share file descriptors with the child. For example, perhaps your applications launches a sub-command and you want any errors to go in the log file, but ignore stdout. For this you’d like to have stderr of the child to be displayed. In this case, libuv supports inheriting file descriptors. In this sample, we invoke the test program, which is:

    proc-streams/test.c

    #include <stdio.h>
    
    int main()
    {
        fprintf(stderr, "This is stderr\n");
        printf("This is stdout\n");
        return 0;
    }
    

    The actual program proc-streams runs this while inheriting only stderr. The file descriptors of the child process are set using the stdio field in uv_process_options_t. First set the stdio_count field to the number of file descriptors being set. uv_process_options_t.stdio is an array of uv_stdio_container_t, which is:

    typedef struct uv_stdio_container_s {
      uv_stdio_flags flags;
    
      union {
        uv_stream_t* stream;
        int fd;
      } data;
    } uv_stdio_container_t;
    

    where flags can have several values. Use UV_IGNORE if it isn’t going to be used. If the first three stdio fields are marked as UV_IGNORE they’ll redirect to /dev/null.

    Since we want to pass on an existing descriptor, we’ll use UV_INHERIT_FD. Then we set the fd to stderr.

    proc-streams/main.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;
    
    
        if (uv_spawn(loop, &child_req, options)) {
            fprintf(stderr, "%s\n", uv_strerror(uv_last_error(loop)));
            return 1;
        }
    
        return uv_run(loop, UV_RUN_DEFAULT);
    }
    

    If you run proc-stream you’ll see that only the line “This is stderr” will be displayed. Try marking stdout as being inherited and see the output.

    It is dead simple to apply this redirection to streams. By setting flags to UV_INHERIT_STREAM and setting data.stream to the stream in the parent process, the child process can treat that stream as standard I/O. This can be used to implement something like CGI.

    A sample CGI script/executable is:

    cgi/tick.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;
    }
    

    The CGI server combines the concepts from this chapter and 网络 so that every client is sent ten ticks after which that connection is closed.

    cgi/main.c

    void on_new_connection(uv_stream_t *server, int status) {
        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);
        }
    }
    

    Here we simply accept the TCP connection and pass on the socket (stream) to invoke_cgi_script.

    cgi/main.c

    void invoke_cgi_script(uv_tcp_t *client) {
    
        /* ... 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 = on_exit;
        options.file = args[0];
        options.args = args;
    
        child_req.data = (void*) client;
        if (uv_spawn(loop, &child_req, options)) {
            fprintf(stderr, "%s\n", uv_strerror(uv_last_error(loop)));
            return;
        }
    }
    

    The stdout of the CGI script is set to the socket so that whatever our tick script prints, gets sent to the client. By using processes, we can offload the read/write buffering to the operating system, so in terms of convenience this is great. Just be warned that creating processes is a costly task.

    管道(Pipes)

    libuv’s uv_pipe_t structure is slightly confusing to Unix programmers, because it immediately conjures up | and pipe(7). But uv_pipe_t is not related to anonymous pipes, rather it has two uses:

    1. Stream API – It acts as the concrete implementation of the uv_stream_t API for providing a FIFO, streaming interface to local file I/O. This is performed using uv_pipe_open as covered in 缓冲区与流(Buffers and Streams). You could also use it for TCP/UDP, but there are already convenience functions and structures for them.
    2. IPC mechanism – uv_pipe_t can be backed by a Unix Domain Socket or Windows Named Pipe to allow multiple processes to communicate. This is discussed below.

    父子进程间通信

    A parent and child can have one or two way communication over a pipe created by settings uv_stdio_container_t.flags to a bit-wise combination of UV_CREATE_PIPE and UV_READABLE_PIPE or UV_WRITABLE_PIPE. The read/write flag is from the perspective of the child process.

    任意进程间通信

    Since domain sockets [1] can have a well known name and a location in the file-system they can be used for IPC between unrelated processes. The D-BUS system used by open source desktop environments uses domain sockets for event notification. Various applications can then react when a contact comes online or new hardware is detected. The MySQL server also runs a domain socket on which clients can interact with it.

    When using domain sockets, a client-server pattern is usually followed with the creator/owner of the socket acting as the server. After the initial setup, messaging is no different from TCP, so we’ll re-use the echo server example.

    pipe-echo-server/main.c

    int main() {
        loop = uv_default_loop();
    
        uv_pipe_t server;
        uv_pipe_init(loop, &server, 0);
    
        signal(SIGINT, remove_sock);
    
        if (uv_pipe_bind(&server, "echo.sock")) {
            fprintf(stderr, "Bind error %s\n", uv_err_name(uv_last_error(loop)));
            return 1;
        }
        if (uv_listen((uv_stream_t*) &server, 128, on_new_connection)) {
            fprintf(stderr, "Listen error %s\n", uv_err_name(uv_last_error(loop)));
            return 2;
        }
        return uv_run(loop, UV_RUN_DEFAULT);
    }
    

    We name the socket echo.sock which means it will be created in the local directory. This socket now behaves no different from TCP sockets as far as the stream API is concerned. You can test this server using netcat:

    $ nc -U /path/to/echo.sock

    A client which wants to connect to a domain socket will use:

    void uv_pipe_connect(uv_connect_t *req, uv_pipe_t *handle, const char *name, uv_connect_cb cb);
    

    where name will be echo.sock or similar.

    通过管道发送文件描述符(Sending file descriptors over pipes)

    The cool thing about domain sockets is that file descriptors can be exchanged between processes by sending them over a domain socket. This allows processes to hand off their I/O to other processes. Applications include load-balancing servers, worker processes and other ways to make optimum use of CPU.

    Warning

    On Windows, only file descriptors representing TCP sockets can be passed around.

    To demonstrate, we will look at a echo server implementation that hands of clients to worker processes in a round-robin fashion. This program is a bit involved, and while only snippets are included in the book, it is recommended to read the full code to really understand it.

    The worker process is quite simple, since the file-descriptor is handed over to it by the master.

    multi-echo-server/worker.c

    uv_loop_t *loop;
    uv_pipe_t queue;
    
    int main() {
        loop = uv_default_loop();
    
        uv_pipe_init(loop, &queue, 1);
        uv_pipe_open(&queue, 0);
        uv_read2_start((uv_stream_t*)&queue, alloc_buffer, on_new_connection);
        return uv_run(loop, UV_RUN_DEFAULT);
    }
    

    queue is the pipe connected to the master process on the other end, along which new file descriptors get sent. We use the read2 function to express interest in file descriptors. It is important to set the ipc argument of uv_pipe_init to 1 to indicate this pipe will be used for inter-process communication! Since the master will write the file handle to the standard input of the worker, we connect the pipe to stdin using uv_pipe_open.

    multi-echo-server/worker.c

    void on_new_connection(uv_pipe_t *q, ssize_t nread, uv_buf_t buf, uv_handle_type pending) {
        if (pending == UV_UNKNOWN_HANDLE) {
            // error!
            return;
        }
    
        uv_pipe_t *client = (uv_pipe_t*) malloc(sizeof(uv_pipe_t));
        uv_pipe_init(loop, client, 0);
        if (uv_accept((uv_stream_t*) q, (uv_stream_t*) client) == 0) {
            fprintf(stderr, "Worker %d: Accepted fd %d\n", getpid(), client->io_watcher.fd);
            uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
        }
        else {
            uv_close((uv_handle_t*) client, NULL);
        }
    }
    

    Although accept seems odd in this code, it actually makes sense. What accept traditionally does is get a file descriptor (the client) from another file descriptor (The listening socket). Which is exactly what we do here. Fetch the file descriptor (client) from queue. From this point the worker does standard echo server stuff.

    Turning now to the master, let’s take a look at how the workers are launched to allow load balancing.

    multi-echo-server/main.c

    uv_loop_t *loop;
    
    struct child_worker {
        uv_process_t req;
        uv_process_options_t options;
        uv_pipe_t pipe;
    } *workers;
    

    The child_worker structure wraps the process, and the pipe between the master and the individual process.

    multi-echo-server/main.c

    void setup_workers() {
        // ...
    
        // 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 = on_exit;
            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);
        }
    }
    

    In setting up the workers, we use the nifty libuv function uv_cpu_info to get the number of CPUs so we can launch an equal number of workers. Again it is important to initialize the pipe acting as the IPC channel with the third argument as 1. We then indicate that the child process’ stdin is to be a readable pipe (from the point of view of the child). Everything is straightforward till here. The workers are launched and waiting for file descriptors to be written to their pipes.

    It is in on_new_connection (the TCP infrastructure is initialized in main()), that we accept the client socket and pass it along to the next worker in the round-robin.

    multi-echo-server/main.c

    void on_new_connection(uv_stream_t *server, int status) {
        if (status == -1) {
            // error!
            return;
        }
    
        uv_pipe_t *client = (uv_pipe_t*) malloc(sizeof(uv_pipe_t));
        uv_pipe_init(loop, client, 0);
        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(".", 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);
        }
    }
    

    Again, the uv_write2 call handles all the abstraction and it is simply a matter of passing in the file descriptor as the right argument. With this our multi-process echo server is operational.

    TODO what do the write2/read2 functions do with the buffers?


    [1]In this section domain sockets stands in for named pipes on Windows as well.
    展开全文
  • Libuv中文文档文件管理

    千次阅读 2016-08-31 16:35:05
    libuv文档

    Libuv中文文档文件管理

    文章目录

    简单的文件读写是通过 uv_fs_* 函数族和与之相关的 uv_fs_t 结构体完成的.

    libuv 提供的文件操作和 socket operations 并不相同. 套接字操作使用了操作系统本身提供了非阻塞操作, 而文件操作内部使用了阻塞函数, 但是 libuv 是在线程池中调用这些函数, 并在应用程序需要交互时通知在事件循环中注册的监视器.

    所有的文件操作函数都有两种形式 – 同步 synchronousasynchronous.

    同步 synchronous 形式如果没有指定回调函数则会被自动调用( 阻塞的 ), 函数的返回值和 Unix 系统的函数调用返回值相同(调用成功通常返回 0, 若出现错误则返回 -1).

    而异步 asynchronous 形式则会在传入回调函数时被调用, 并且返回 0.

    读写文件

    文件描述符可以采用如下方式获得:

    int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, int mode, uv_fs_cb cb)

    参数 flagsmode 和标准的 Unix flags 相同. libuv 会小心地处理 Windows 环境下的相关标志位(flags)的转换, 所以编写跨平台程序时你不用担心不同平台上文件打开的标志位不同。

    关闭文件描述符可以使用:

    int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb)

    与文件系统相关的操作的回调函数具有如下签名:

    void callback(uv_fs_t* req);

    让我们来看看 cat 命令的一个简单实现吧: 我们首先注册一个在文件打开时的回调函数 (顾名思义, 该函数将在文件打开时被调用).

    uvcat/main.c – opening a file

    void on_open(uv_fs_t *req) {
        if (req->result != -1) {
            uv_fs_read(uv_default_loop(), &read_req, req->result,
                       buffer, sizeof(buffer), -1, on_read);
        }
        else {
            fprintf(stderr, "error opening file: %d\n", req->errorno);
        }
        uv_fs_req_cleanup(req);
    }
    

    uv_fs_tresult 字段在执行 us_fs_open 时代表一个文件描述符, 如果文件成功被打开, 我们开始读取文件.

    必须调用 uv_fs_req_cleanup() 来释放 libuv 内部使用的内存空间.

    uvcat/main.c – read callback

    void on_read(uv_fs_t *req) {
        uv_fs_req_cleanup(req);
        if (req->result < 0) {
            fprintf(stderr, "Read error: %s\n", uv_strerror(uv_last_error(uv_default_loop())));
        }
        else if (req->result == 0) {
            uv_fs_t close_req;
            // synchronous
            uv_fs_close(uv_default_loop(), &close_req, open_req.result, NULL);
        }
        else {
            uv_fs_write(uv_default_loop(), &write_req, 1, buffer, req->result, -1, on_write);
        }
    }
    

    在调用 read 时, 你应该传递一个初始化的缓冲区, 在 read 回调函数被触发(调用之前), 该缓冲区将会被填满数据.

    在 read 的回调函数中 result 如果是 0, 则读取文件时遇到了文件尾(EOF), -1 则代表出现了错误, 而正整数则是表示成功读取的字节数.

    此处给你展示了编写异步程序的通用模式, uv_fs_close() 是异步调用的. 通常如果任务是一次性的, 或者只在程序启动和关闭时被执行的话都可以采用同步方式执行, 因为我们期望提高 I/O 效率, 采用异步编程时程序也可以做一些基本的任务并处理多路 I/O.. 对于单个任务而言性能差异可以忽略, 但是代码却能大大简化.

    我们可以总结出真正的系统调用返回值一般是存放在 uv_fs_t.result.

    写入文件与上述过程类似, 使用 uv_fs_write 即可. write 的回调函数在写入完成时被调用.. 在我们的程序中回调函数只是只是简单地发起了下一次读操作, 因此, 读写操作会通过回调函数连续进行下去.

    uvcat/main.c – write callback

    void on_write(uv_fs_t *req) {
        uv_fs_req_cleanup(req);
        if (req->result < 0) {
            fprintf(stderr, "Write error: %s\n", uv_strerror(uv_last_error(uv_default_loop())));
        }
        else {
            uv_fs_read(uv_default_loop(), &read_req, open_req.result, buffer, sizeof(buffer), -1, on_read);
        }
    }
    

    Note

    错误值通常保存在 errno 并可以通过 uv_fs_t.errorno 获取, 但是被转换成了标准的 UV_* 错误码. 目前还没有方法直接从 errorno 解析得到错误消息的字符串表示.

    Warning

    由于文件系统和磁盘通常为了提高性能吞吐率而配置了缓冲区, libuv 中一次 ‘成功’ 的写操作可能不会被立刻提交到磁盘上, 你可以通过 uv_fs_fsync 来保证一致性.

    我们再来看看 main 函数中设置的多米诺骨牌吧(原作者意指在 main 中设置回调函数后会触发整个程序开始执行):

    uvcat/main.c

    int main(int argc, char **argv) {
        uv_fs_open(uv_default_loop(), &open_req, argv[1], O_RDONLY, 0, on_open);
        uv_run(uv_default_loop(), UV_RUN_DEFAULT);
        return 0;
    }
    

    文件系统相关操作(Filesystem operations)

    所有的标准文件系统操作, 例如 unlink, rmdir, stat 都支持异步操作, 并且各个函数的参数非常直观. 他们和 read/write/open 的调用模式一致, 返回值都存放在 uv_fs_t.result 域. 完整的列表如下:

    Filesystem operations

    UV_EXTERN int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file file,
        uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path,
        int flags, int mode, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file file,
        void* buf, size_t length, int64_t offset, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
        uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_write(uv_loop_t* loop, uv_fs_t* req, uv_file file,
        void* buf, size_t length, int64_t offset, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path,
        int mode, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path,
        uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_readdir(uv_loop_t* loop, uv_fs_t* req,
        const char* path, int flags, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path,
        uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_fstat(uv_loop_t* loop, uv_fs_t* req, uv_file file,
        uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path,
        const char* new_path, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_fsync(uv_loop_t* loop, uv_fs_t* req, uv_file file,
        uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_fdatasync(uv_loop_t* loop, uv_fs_t* req, uv_file file,
        uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_ftruncate(uv_loop_t* loop, uv_fs_t* req, uv_file file,
        int64_t offset, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file out_fd,
        uv_file in_fd, int64_t in_offset, size_t length, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_chmod(uv_loop_t* loop, uv_fs_t* req, const char* path,
        int mode, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path,
        double atime, double mtime, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file file,
        double atime, double mtime, uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path,
        uv_fs_cb cb);
    
    UV_EXTERN int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path,
        const char* new_path, uv_fs_cb cb);
    

    回调函数中应该调用 uv_fs_req_cleanup() 函数来释放 uv_fs_t 参数占用的内存.

    缓冲区与流(Buffers and Streams)

    libuv 中基本的 I/O 工具是流(uv_stream_t). TCP 套接字, UDP 套接字, 文件, 管道, 和进程间通信都可以作为 的子类.

    (Streams) 通过每个子类特定的函数来初始化, 然后可以通过如下函数进行操作:

    int uv_read_start(uv_stream_t*, uv_alloc_cb alloc_cb, uv_read_cb read_cb);
    int uv_read_stop(uv_stream_t*);
    int uv_write(uv_write_t* req, uv_stream_t* handle,
                uv_buf_t bufs[], int bufcnt, uv_write_cb cb);
    

    基于流的函数比上面介绍的文件系统相关的函数更容易使用, libuv 在调用 uv_read_start 后会自动从流中读取数据, 直到调用了 uv_read_stop.

    用于保存数据的单元被抽象成了 buffer 结构 – uv_buf_t. 它其实只保存了指向真实数据的指针(uv_buf_t.base) 以及真实数据的长度 (uv_buf_t.len). uv_buf_t 本身是轻量级的, 通常作为值被传递给函数, 真正需要进行内存管理的是 buffer 结构中的指针所指向的真实数据, 通常由应用程序申请分配并释放.

    为了示范流的用法, 我们借助了(管道) uv_pipe_t , 这使得我们把本地文件变成了流[#]_. 下面是利用 libuv 实现的一个简单的 tee . 将所有的操作变成了异步方式后, 事件 I/O 的强大能力便展现出来. 两个写操作并不会阻塞对方, 但是我们必须小心地拷贝数据至缓冲区, 并确保在写入数据之前缓冲区不被释放.

    该程序按照如下方式执行:

    ./uvtee <output_file>

    我们在指定的文件上打开了一个管道, libuv 的文件管道默认是双向打开的.

    uvtee/main.c – read on pipes

    int main(int argc, char **argv) {
        loop = uv_default_loop();
    
        uv_pipe_init(loop, &stdin_pipe, 0);
        uv_pipe_open(&stdin_pipe, 0);
    
        uv_pipe_init(loop, &stdout_pipe, 0);
        uv_pipe_open(&stdout_pipe, 1);
        
        uv_fs_t file_req;
        int fd = uv_fs_open(loop, &file_req, argv[1], O_CREAT | O_RDWR, 0644, NULL);
        uv_pipe_init(loop, &file_pipe, 0);
        uv_pipe_open(&file_pipe, fd);
    
        uv_read_start((uv_stream_t*)&stdin_pipe, alloc_buffer, read_stdin);
    
        uv_run(loop, UV_RUN_DEFAULT);
        return 0;
    }
    

    若是 IPC 或命名管道, uv_pipe_init() 的第三个参数应该设置为 1, 我们会在 进程 一节对此作出详细解释. 调用 uv_pipe_open() 将文件描述符和文件关联在了一起.

    我们开始监控标准输入 stdin. 回调函数 alloc_buffer 为程序开辟了一个新的缓冲区来容纳新到来的数据. read_stdin 也会被调用, 并且 uv_buf_t 作为调用参数.

    uvtee/main.c – reading buffers

    uv_buf_t alloc_buffer(uv_handle_t *handle, size_t suggested_size) {
        return uv_buf_init((char*) malloc(suggested_size), suggested_size);
    }
    
    void read_stdin(uv_stream_t *stream, ssize_t nread, uv_buf_t buf) {
        if (nread == -1) {
            if (uv_last_error(loop).code == UV_EOF) {
                uv_close((uv_handle_t*)&stdin_pipe, NULL);
                uv_close((uv_handle_t*)&stdout_pipe, NULL);
                uv_close((uv_handle_t*)&file_pipe, NULL);
            }
        }
        else {
            if (nread > 0) {
                write_data((uv_stream_t*)&stdout_pipe, nread, buf, on_stdout_write);
                write_data((uv_stream_t*)&file_pipe, nread, buf, on_file_write);
            }
        }
        if (buf.base)
            free(buf.base);
    }
    

    此处使用标准的 malloc 已经可以足够, 但是你也可以指定其他的内存分配策略. 例如, node.js 使用自己特定的 slab 分配器.

    在任何情况下出错, read 回调函数 nread 参数都为 -1. 出错原因可能是 EOF(遇到文件尾), 在此种情况下我们使用 ‘’uv_close()’’ 函数关闭所有的流, uv_close() 会根据所传递进来句柄的内部类型来自动处理. 如果没有出现错误, nread 是一个非负数, 意味着我们可以向输出流中写入 nread 字节的数据. 最后记住一点, 缓冲区 buffer 的分配和释放是由应用程序负责的, 所以记得释放不再使用的内存空间.

    uvtee/main.c – Write to pipe

    typedef struct {
        uv_write_t req;
        uv_buf_t buf;
    } write_req_t;
    
    void free_write_req(uv_write_t *req) {
        write_req_t *wr = (write_req_t*) req;
        free(wr->buf.base);
        free(wr);
    }
    
    void on_stdout_write(uv_write_t *req, int status) {
        free_write_req(req);
    }
    
    void on_file_write(uv_write_t *req, int status) {
        free_write_req(req);
    }
    
    void write_data(uv_stream_t *dest, size_t size, uv_buf_t buf, uv_write_cb callback) {
        write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t));
        req->buf = uv_buf_init((char*) malloc(size), size);
        memcpy(req->buf.base, buf.base, size);
        uv_write((uv_write_t*) req, (uv_stream_t*)dest, &req->buf, 1, callback);
    }
    

    write_data() 将读取的数据拷贝一份至缓冲区 req->buf.base, 同样地, 当 write 完成后回调函数被调用时, 该缓冲区也并不会被传递到回调函数中, 所以, 为了绕过这一缺点, 我们将写请求和缓冲区封装在 write_req_t 结构体中, 然后在回调函数中解封该结构体来获取相关参数.

    Warning

    If your program is meant to be used with other programs it may knowingly or unknowingly be writing to a pipe. This makes it susceptible to aborting on receiving a SIGPIPE. It is a good idea to insert:

    signal(SIGPIPE, SIG_IGN)

    in the initialization stages of your application.

    文件变更事件(File change events)

    现代操作系统都提供了 API 用来在单独的文件或文件夹上设置监视器, 当文件被修改时应用程序会得到通知, libuv 也封装了常用的文件变更通知程序库. 这是 libuv 中最不一致的部分了, 文件变更通知系统本身在不同的系统中实现起来差别非常大, 因此让所有的事情在每个平台上都完美地工作将变得异常困难, 为了给出一个示例,我写了一个简单的工具, 该函数按照如下命令行运行, 并监视指定的文件.

    ./onchange <command> <file1> [file2] …

    文件变更通知通过 uv_fs_event_init() 启动:

    onchange/main.c – The setup

    while (argc-- > 2) {
        fprintf(stderr, "Adding watch on %s\n", argv[argc]);
        uv_fs_event_init(loop, (uv_fs_event_t*) malloc(sizeof(uv_fs_event_t)), argv[argc], run_command, 0);
    }

    第三个参数是实际监控的文件或者文件夹, 最后一个参数 flags 可取值如下:

     UV_FS_EVENT_WATCH_ENTRY = 1,
      UV_FS_EVENT_STAT = 2,
      UV_FS_EVENT_RECURSIVE = 3
    

    若设置 UV_FS_EVENT_WATCH_ENTRYUV_FS_EVENT_STAT 不做任何事情(目前). 设置了 UV_FS_EVENT_RECURSIVE 将会监视子文件夹(需 libuv 支持).

    回调函数将接受以下参数:

    1. uv_fs_event_t *handle – 监视器. filename
      字段是该监视器需要监视的文件.
    2. const char *filename – 如果监视目录, 则该参数指明该目录中发生了变更的文件,
      在 Linux 和 Windows 平台上可以是非 null.
    3. int flagsUV_RENAMEUV_CHANGE.
    4. int status – 目前为 0.

    我们的例子只是简单地打印出参数, 并通过 system 函数运行指定命令.

    onchange/main.c – file change notification callback

    void run_command(uv_fs_event_t *handle, const char *filename, int events, int status) {
        fprintf(stderr, "Change detected in %s: ", handle->filename);
        if (events == UV_RENAME)
            fprintf(stderr, "renamed");
        if (events == UV_CHANGE)
            fprintf(stderr, "changed");
    
        fprintf(stderr, " %s\n", filename ? filename : "");
        system(command);
    }
    
    展开全文
  •   官方文档链接点我点我点我   https://github.com/luohaha/Chinese-uvbook http://luohaha.github.io/Chinese-uvbook/ 中文教程 https://github.com/nikhilm/uvbook ...
  • libuv文档
  • Libuv中文文档之线程

    千次阅读 2016-08-31 16:37:10
    libuv
  • Libuv中文文档序言

    2016-08-31 16:32:49
    libuv文档
  • libuv book 中文文档 An introduction to libuv libuv book 中文文档 An introduction to libuv
  • libuv中文教程.pdf

    2020-03-01 18:26:23
    libvu官方教程的中文翻译版,详细讲述了libuv各个功能模块的使用方法,适用于刚入门学习libuv的人们
  • libuv开发说明中文

    2019-02-01 12:51:54
    一个跨平台的多线程开发库,使用语言C/C++,API调用方式类似于linux pthread
  • libuv中文教程

    2018-07-12 20:31:09
    翻译自《 An Introduction to libuv》,使用gitbook制作成了pdf文档libuv很不错的中文编程指南。
  • An Introduction to libuv中文版来自于一个github上的帮助文档的翻译项目,原项目地址为https://github.com/forhappy/uvbook,这个项目是libuv这个C++库的中文帮助文档。但它是rst格式的文档,需要用python工具...
  • Libuv编程指南

    2021-05-20 18:28:37
    中文文档:http://luohaha.github.io/Chinese-uvbook/ 一、简介 libuv是一个支持多平台的异步IO库,它主要是为了node.js而开发的,但是也可以用于Luvit, Julia, pyuv及其他软件。 libuv强制使用异步的,事件...
  • An Introduction to libuv中文版来自于一个github上的帮助文档的翻译项目,原项目地址为https://github.com/forhappy/uvbook,这个项目是libuv这个C++库的中文帮助文档。但它是rst格式的文档,需要用python工具...
  • libuv中文API手册(1)

    千次阅读 2016-04-02 21:38:29
    官方文档URL官方文档链接点我点我点我1. 概述 libuv是一个支持多平台的异步IO库。它主要是为了node.js而开发的,但是也可以用于Luvit, Julia, pyuv及其他软件。 注意:如果您发现了此软件中的错误,那么请提交...
  • libuv 中文编程指南

    千次阅读 2018-01-01 05:06:47
    最近看了一些有关 libuv 的东西,另外复习了一些与同步、异步、阻塞、非阻塞,异步IO(aio)的东西, 算是技术积累吧,等有时间了整理出一个完整的文档出来,希望在今后的编程中用到。 不多说了,本文是今后几篇...
  • libuv 是一个高性能的,事件驱动的I/O,并且支持多平台的网络库,接下来就手把手带你进入libuv编译安装的学习...
  • libuv 中文编程指南---网络

    千次阅读 2016-11-21 12:20:29
    libuv 的网络接口与 BSD 套接字接口存在很大的不同, 某些事情在 libuv 下变得更简单了, 并且所有接口都是都是非阻塞的, 但是原则上还是一致的. 另外 libuv 也提供了一些工具类的函数抽象了一些让人生厌的, 重复而...
  • Server log4x.h log4x.c ​ 初始化log系统:记录日志日期、时间、文件名和行号、log记录的最低级别、屏幕显示日志内容 ​ 关闭log系统 ​ 记录日志 ?stb_sprintf.h ...发送一个电子文档,发送前,我先得到MD
  • 序言 本书由一系列 libuv 教程组成, ...本书旨在涵盖 libuv 的主要特性, 并不是一份完整介绍 libuv 内部每个 API 和数据结构的指南, 官方文档 official libuv documentation 可以直接在 libuv 源码提供的头...
  • 一开始,我得向Libuv库和Libuv库开发者以及相关粉丝们道一个歉,对不起,我错怪你们了。深深感到自己的无知,是多么羞愧的事情!! 事情的经过是这样的。 原先按照公司要求,我在开发Windows版的TCP服务器时,使用了...
  • 最近看了一些有关 libuv ...不多说了,本文是今后几篇《libuv 中文编程指南》的前言,先介绍下 libuv 究竟是干什么的吧,看看 开源中国社区收录的有关 libuv 的介绍, libuv 是 Node 的新跨平台抽象层,用于抽...
  • Whoosh 自带的是英文分词,对中文分词支持不太好,使用 jieba 替换 whoosh 的分词组件。例如,如果您正在创建博客软件,您可以使用whoosh添加搜索功能,允许用户搜索博客条目。 这是 Pygments 的家。它是一种通用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 573
精华内容 229
关键字:

libuv 中文文档

友情链接: 345.rar