libuv-helloworld

io事件通知模型

libuv 是nodejs衍生出来的一个库。关于io事件,历史上操作系统有两种处理模型。通俗讲就是当有一个io消息来时,操作系统如何通知你?

第一种方式,进程阻塞。操作系统初期,当你有一个可读或者可写io时,一般会搞一个新的进程,当你系统调用read 或者write时会被阻塞住,操作系统会标记这个进程为阻塞进程。当io完成后,操作系统会把标记为阻塞的进程标记去掉。这样,你的进程将进入待执行队列。当然还有一种情况时非阻塞的,就是read或者write时,直接返回。但是需要你自己去死循环check这个io操作是否有新的状态。

第二种方式,多路复用。后来啊,人们觉得前一种方式搞了太多的进程或者线程出来。进程间切换,以及进程本身需要占用一定量内存开销。这对于当有大量高并发io场景比较吃力。所以操作系统提供了第二种方式,多路复用。即操作系统提供了另外一种api,select。这个api一次可以监听多个io 描述符。io有状态变化时才通知用户进程。就不需要开辟很多进程了。当然随着对性能要求更高,操作系统不断优化api,从select 到poll,再到epoll等等。

咱们nodejs当然会选择后一种事件模型,但是有个问题,刚才说epoll这种只是linux的。其它平台有自己的实现。比如其它类unix的是Kqueue,window是IOCP。最开始nodejs选择libev的库,这个库不兼容windows平台。所以自己搞了一个libuv。 参考:http://nikhilm.github.io/uvbook/introduction.html

libuv api风格

libuv helloworld代码

#include <stdio.h>
#include <stdlib.h>
#include <uv.h>

int main() {
    uv_loop_t *loop = malloc(sizeof(uv_loop_t));
    uv_loop_init(loop);

    printf("Now quitting.\n");
    uv_run(loop, UV_RUN_DEFAULT);

    uv_loop_close(loop);
    free(loop);
    return 0;
}

libuv timer


#include <assert.h>
#include <sys/time.h>
#include "uv.h"


// gcc -Wall timer.c -o  timer -luv

int repeat_cb_called = 0;

static long timestamp() {    
    struct timeval tv;
    gettimeofday(&tv,NULL);
    return tv.tv_sec*1000 + tv.tv_usec/1000;  //毫秒
}

static void repeat_close_cb(uv_handle_t* handle) {
    printf("REPEAT_CLOSE_CB\n");
}


static void repeat_cb(uv_timer_t* handle) {
    printf("[%ld]REPEAT_CB %d \n",timestamp(),repeat_cb_called+1);

    assert(handle != NULL);
    assert(1 == uv_is_active((uv_handle_t*) handle));

    repeat_cb_called++;

    if (repeat_cb_called == 5) {
      uv_close((uv_handle_t*)handle, repeat_close_cb);
    }
}


int main() {
    uv_timer_t once;
    int ret;
    ret = uv_timer_init(uv_default_loop(), &once);
    printf("[%ld]start timer\n",timestamp());
    ret = uv_timer_start(&once, repeat_cb, 100,1000); // 100毫秒之后第一次触发,之后的每1000毫秒触发一次
    uv_run(uv_default_loop(), UV_RUN_DEFAULT);
    // uv_run(uv_default_loop(), UV_RUN_ONCE); // 只运行一次
}

从上面两个例子中,我们找出了规律。 第一,你会发现一个uv_xxx_t结构体,并且一定有一个uv_xxx_init。 第二,从下面的参考更多例子中,你会发现有很多这样的结构体,why?

我们先回答第一个问题,其实答案已经在第一个代码demo中。c语言风格的程序,内存管理需要透明。这种设计可以使我们自由为uv_xxx_t分配以及释放内存。

第二个问题,为啥有一堆uv_xxx_t这样的结构体呢? libuv从用户角度划分了多种业务类型。但是这些业务类型又不能高度抽像为同一个结构体比如timer 和 tcp 肯定有不同的业务属性,所以抽像了不同的结构体。以下为抽象的结构体。

uv_loop_t — Event loop
uv_handle_t — Base handle
uv_req_t — Base request
uv_timer_t — Timer handle
uv_prepare_t — Prepare handle
uv_check_t — Check handle
uv_idle_t — Idle handle
uv_async_t — Async handle
uv_poll_t — Poll handle
uv_signal_t — Signal handle
uv_process_t — Process handle
uv_stream_t — Stream handle
uv_tcp_t — TCP handle
uv_pipe_t — Pipe handle
uv_tty_t — TTY handle
uv_udp_t — UDP handle
uv_fs_event_t — FS Event handle
uv_fs_poll_t — FS Poll handle

参考:

官方文档

http://docs.libuv.org/en/v1.x/api.html

基本概念,自定义的内存管理

https://github.com/feixiao/Chinese-uvbook/blob/master/source/basics_of_libuv.md

更多例子 https://github.com/feixiao/learning-libuv/blob/master/ReadMe.md