libuv 编译

在官网(https://libuv.org) 上下载libuv的源码,对源码进行编译

  • 目录层级结构

-w200

  • 添加头文件的搜索路径

-w600

  • 配置windows上依赖的lib库

-w600

libuv 简介

libuv 是开源跨平台的异步IO库,包括网络异步、文件异步等
libuv 事件循环模型:epoll、kqueue、IOCP、 event prorts
除此之外还包括有:异步Tcp 与 UDP Sockets、DNS解析、异步文件读写、信号处理、高性能定时器、进程/线程池

libuv 原理

  1. 在用户层同时管理多个句柄请求,从而实现异步
  2. 所有的事件循环和句柄等请求都交给loop进行统一管理,例如:loop监听所有的socket,当有数据来时,loop就会进行处理,然后转到用户自定义的回调函数中
  3. libuv编程思想:
    1. 创建一个对象,例如socket
    2. 将对象交给loop进行管理
    3. 指定一个回调函数,当有事件发生时,调用这个回调函数

架构图

-w600

事件循环loop 流程图

loop循环对象

static uv_loop_t* loop = NULL;
loop = uv_default_loop();
uv_run(loop, UV_RUN_DEFAULT);

这里需要注意的是:根据事件循环的流程图分析,如果当前loop中没有任何事件等待,就会结束事件循环

libuv Tcp 代码实现

  • 首先我们需要创建loop对象和一个Tcp的监听句柄

static uv_loop_t* loop = NULL; static uv_tcp_t l_server; // 监听句柄 int main(int argc, char** argv) { int ret; loop = uv_default_loop(); // Tcp 监听服务 uv_tcp_init(loop, &l_server); // 将l_server监听句柄加入到event loop uv_run(loop, UV_RUN_DEFAULT); failed: printf("end\n"); system("pause"); return 0; }

这时loop中没有任何对象,程序在运行时,会直接结束

  • 创建socket 并进行绑定
struct sockaddr_in addr;
uv_ip4_addr("0.0.0.0", 6080, &addr);
ret = uv_tcp_bind(&l_server, (const struct sockaddr*)&addr, 0);

if (ret != 0) {
    goto failed;
}
  • 配置 event loop 监听管理
uv_listen((uv_stream_t*)&l_server, SOMAXCONN, on_connection);

这里我们需要实现一个回调函数,当有人连接时,event loop会调用我们指向的回调函数
ps:如果不清楚回调函数的格式,可以F12找到函数的定义
-w500

  • on_connection回调函数实现
// uv_listen callback
static void on_connection(uv_stream_t* server, int statu) {

    printf("New client coming\n");

    uv_tcp_t* client = malloc(sizeof(uv_tcp_t));
    memset(client, 0, sizeof(uv_tcp_t));

    uv_tcp_init(loop, client);
    uv_accept(server, (uv_stream_t*)client);

    // 通知event loop 进行管理
    uv_read_start((uv_stream_t*)client, uv_alloc_buf, after_read);
}

实现连接回调时,需要通知event loop,调用uv_read_start函数,这时需要另外实现两个回调函数,uv_alloc_buf、after_read

  • uv_alloc_buf、after_read回调实现
static void uv_alloc_buf(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {

    if (handle->data != NULL) {

        free(handle->data);
        handle->data = NULL;
    }

    buf->base = malloc(suggested_size + 1);
    buf->len = suggested_size;

    handle->data = buf->base;
}

此函数的作用是:给 event loop准备好读入数据的内存
把准备好的内存通过 buf 递交给 event loop

static void after_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {

    // 连接断开
    if (nread < 0) {

        uv_shutdown_t* reg = malloc(sizeof(uv_shutdown_t));
        memset(reg, 0, sizeof(uv_shutdown_t));

        uv_shutdown(reg, stream, on_shutdown);
        return;
    }

    buf->base[nread] = 0;
    printf("recv %d bits\n", nread);
    printf("%s\n", buf->base);

    uv_write_t* w_req = malloc(sizeof(uv_write_t));
    uv_buf_t* w_buf = malloc(sizeof(uv_buf_t));

    w_buf->base = buf->base;
    w_buf->len = nread;
    // 将buf带入到req中 方便在callback中 free此内存
    w_req->data = w_buf;
    uv_write(w_req, stream, w_buf, 1, after_write);
}

此函数的作用时间为:当 event loop 读完内存时进行回掉调
在这里处理连接断开、接收客户端的信息,并发送回执
需要对断开的回调函数及发送的回调函数进行实现

  • on_shutdown、after_write 回调实现
static void after_write(uv_write_t* req, int status) {

    if (status == 0) {

        printf("Write sucess\n");
    }

    uv_write_t* w_req = req->data;

    if (w_req) {

        free(w_req);
    }

    free(req);
}

发送完毕后,需要对内存进行维护,在发送之前将buf的内存发送给req中,在这里释放发送时占用的内存

static void on_close(uv_handle_t* handle) {

    printf("Close Client\n");

    if (handle->data) {

        free(handle->data);
        handle->data = NULL;
    }
}

static void on_shutdown(uv_shutdown_t* req, int status) {

    uv_close((uv_handle_t*)req->handle, on_close);
    free(req);
}

这里是对连接断开回调的实现

  • 运行结果

-w300

  • Server源码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "uv.h"

static uv_loop_t* loop = NULL;
static uv_tcp_t l_server;               // 监听句柄

/*
    数据结构对比

    uv_handle_t 数据结构
    UV_HANDLE_FIELDS

    uv_stream_t 数据结构
    UV_HANDLE_FIELDS
    UV_STREAM_FIELDS

    uv_tcp_t 数据结构
    UV_HANDLE_FIELDS
    UV_STREAM_FIELDS
    UV_TCP_PRIVATE_FIELDS

    uv_tcp_t is uv_stream_t is uv_handle_t
*/

static void on_close(uv_handle_t* handle) {

    printf("Close Client\n");

    if (handle->data) {

        free(handle->data);
        handle->data = NULL;
    }
}

static void on_shutdown(uv_shutdown_t* req, int status) {

    uv_close((uv_handle_t*)req->handle, on_close);
    free(req);
}

static void after_write(uv_write_t* req, int status) {

    if (status == 0) {

        printf("Write sucess\n");
    }

    uv_write_t* w_req = req->data;

    if (w_req) {

        free(w_req);
    }

    free(req);
}

// 当event loop 读完内存时 进行回掉
// strean:发生事件的handle = uv_tcp_t
// nread:读到了多少字节
// buf:数据读到了那个buf里面 buf->base
static void after_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {

    // 连接断开
    if (nread < 0) {

        uv_shutdown_t* reg = malloc(sizeof(uv_shutdown_t));
        memset(reg, 0, sizeof(uv_shutdown_t));

        uv_shutdown(reg, stream, on_shutdown);
        return;
    }

    buf->base[nread] = 0;
    printf("recv %d bits\n", nread);
    printf("%s\n", buf->base);

    uv_write_t* w_req = malloc(sizeof(uv_write_t));
    uv_buf_t* w_buf = malloc(sizeof(uv_buf_t));

    w_buf->base = buf->base;
    w_buf->len = nread;
    // 将buf带入到req中 方便在callback中 free此内存
    w_req->data = w_buf;
    uv_write(w_req, stream, w_buf, 1, after_write);
}

// 当event loop 检测到 HANDLE 上有数据可以读时 进行回掉
// 函数给event loop 准备好读入数据的内存
// handle:发生读事件的handle
// suggested_size:建议我们分配多大的内存
// buf:我们准备好的内存通过buf 告诉event loop
static void uv_alloc_buf(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {

    if (handle->data != NULL) {

        free(handle->data);
        handle->data = NULL;
    }

    buf->base = malloc(suggested_size + 1);
    buf->len = suggested_size;

    handle->data = buf->base;
}

// uv_listen callback
static void on_connection(uv_stream_t* server, int statu) {

    printf("New client coming\n");

    uv_tcp_t* client = malloc(sizeof(uv_tcp_t));
    memset(client, 0, sizeof(uv_tcp_t));

    uv_tcp_init(loop, client);
    uv_accept(server, (uv_stream_t*)client);

    // 通知event loop 进行管理
    uv_read_start((uv_stream_t*)client, uv_alloc_buf, after_read);
}


int main(int argc, char** argv) {

    int ret;
    loop = uv_default_loop();
    // Tcp 监听服务
    uv_tcp_init(loop, &l_server);       // 将l_server监听句柄加入到event loop
    // 配置event loop 管理类型

    struct sockaddr_in addr;
    uv_ip4_addr("0.0.0.0", 6080, &addr);
    ret = uv_tcp_bind(&l_server, (const struct sockaddr*)&addr, 0);

    if (ret != 0) {
        goto failed;
    }

    // 1. event loop 监听管理
    //    当有人连接时 event loop 就会调用uv_connection
    uv_listen((uv_stream_t*)&l_server, SOMAXCONN, on_connection);

    uv_run(loop, UV_RUN_DEFAULT);

failed:
    printf("end\n");
    system("pause");

    return 0;
}

目前内存管理还比较粗糙,之后会对内存管理做相应的改进


What doesn’t kill you makes you stronger.