libuv 编译
在官网(https://libuv.org) 上下载libuv的源码,对源码进行编译
- 目录层级结构

- 添加头文件的搜索路径

- 配置windows上依赖的lib库

libuv 简介
libuv 是开源跨平台的异步IO库,包括网络异步、文件异步等
libuv 事件循环模型:epoll、kqueue、IOCP、 event prorts
除此之外还包括有:异步Tcp 与 UDP Sockets、DNS解析、异步文件读写、信号处理、高性能定时器、进程/线程池
libuv 原理
- 在用户层同时管理多个句柄请求,从而实现异步
- 所有的事件循环和句柄等请求都交给loop进行统一管理,例如:loop监听所有的socket,当有数据来时,loop就会进行处理,然后转到用户自定义的回调函数中
- libuv编程思想:
- 创建一个对象,例如socket
- 将对象交给loop进行管理
- 指定一个回调函数,当有事件发生时,调用这个回调函数
架构图

事件循环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找到函数的定义

- 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);
}
这里是对连接断开回调的实现
- 运行结果

- 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;
}
目前内存管理还比较粗糙,之后会对内存管理做相应的改进
Comments | NOTHING