一. 简介
Libevent、libev、libuv三个网络库,都是c语言实现的异步事件库Asynchronous event library)。
事件(Event):事件是异步事件通知机制的核心,比如fd事件、超时事件、信号事件、定时器事件。有时候也称事件为事件处理器(EventHandler),这个名称更形象,因为Handler本身表示了包含处理所需数据(或数据的地址)和处理的方法(回调函数),更像是面向对象思想中的称谓。
事件循环(EventLoop):等待并分发事件。事件循环用于管理事件。
对于应用程序来说,这些只是异步事件库提供的API,封装了异步事件库跟操作系统的交互,异步事件库会选择一种操作系统提供的机制来实现某一种事件,比如利用Unix/Linux平台的epoll机制实现网络IO事件,在同时存在多种机制可以利用时,异步事件库会采用最优机制。
对比下三个库:
libevent :名气最大,应用最广泛,历史悠久的跨平台事件库;
libev :较libevent而言,设计更简练,性能更好,但对Windows支持不够好;
libuv :开发node的过程中需要一个跨平台的事件库,他们首选了libev,但又要支持Windows,故重新封装了一套,linux下用libev实现,Windows下用IOCP实现;
可见,目前libuv的影响力最大,其次是libevent,libev关注的人较少。
libuv 采用了 异步 (asynchronous), 事件驱动 (event-driven)的编程风格,其主要任务是为开人员提供了一套事件循环和基于I/O(或其他活动)通知的回调函数,libuv 提供了一套核心的工具集,例如定时器,非阻塞网络编程的支持,异步访问文件系统,子进程以及其他功能。
二. 安装libuv
从githup上下载libuv。
$cd libuv 进入到libuv源码目录下,准备编译。
依次运行如下命令:
$ ./autogen.sh
$ ./configure
$ make clean;make
$ make install
这时,会在/usr/local/lib目录下生成libuv.so和libuv.a两个库。
为了方便部署程序,我们使用静态库,将libuv.a拷贝出来放到自己的项目中。
三.编写hello world代码
源文件:
/*
* hello.cpp
* empty msg loop
* 这个例子新建了一个消息队列,但队列里没有任何消息,程序直接退出
*/
#include <stdlib.h>
#include <stdio.h>
#include "libuv/uv.h"
int main()
{
uv_loop_t *loop = uv_loop_new(); //可以理解为新建一个消息队列
uv_run(loop, UV_RUN_DEFAULT); //启动消息队列的消费,UV_RUN_DEFAULT模式下,当消息数为0时,就会退出消息循环
printf("hello world!\n");
return 0;
}
Makefile文件:
INCLUDE = -I./include
LIB = -L./lib -luv -lrt
all:
g++ -lpthread -o hello hello.cpp $(INCLUDE) $(LIB)
clean:
rm -rf hello
四.编写tcp代码
libuv 的网络接口与 BSD 套接字接口存在很大的不同, 某些事情在 libuv 下变得更简单了, 并且所有接口都是都是非阻塞的, 但是原则上还是一致的。
libuv 中在网络 I/O 中使用了 uv_tcp_t 和 uv_udp_t 两个结构体。
tcp-server端代码:
服务器端的 sockets 处理流程如下:
1. uv_tcp_init 初始化 TCP 监视器.
2. uv_tcp_bind 绑定.
3. 在指定的监视器上调用 uv_listen 来设置回调函数, 当有新的客户端连接到来时, libuv 就会调用设置的回调函数.
4. uv_accept 接受连接.
5. 使用 stream operations 与客户端进行通信.
以下是一个简单的 tcp 服务器的例子,
该例会把从client接收到数据转化为大写字母,并回复给client:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "libuv/uv.h"
#define DEFAULT_PORT 8000
#define DEFAULT_BACKLOG 128
uv_loop_t *loop;
struct sockaddr_in addr;
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
buf->base = (char*) malloc(suggested_size);
buf->len = suggested_size;
}
void echo_write(uv_write_t *req, int status)
{
if (status < 0) {
fprintf(stderr, "Write error %s\n", uv_strerror(status));
}
free(req);
}
void echo_read(uv_stream_t *client, 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*) client, NULL);
}
else if (nread > 0)
{
printf("echo_read:%s\r\n",buf->base);
uv_write_t *req = (uv_write_t *) malloc(sizeof(uv_write_t));
int i=0;
while(i < nread)
{
buf->base[i] = toupper(buf->base[i]);
i++;
}
uv_buf_t wrbuf = uv_buf_init(buf->base, nread);
uv_write(req, client, &wrbuf, 1, echo_write);
}
if (buf->base)
{
free(buf->base);
}
}
void on_new_connection(uv_stream_t *server, int status)
{
if (status < 0)
{
fprintf(stderr, "New connection error %s\n", uv_strerror(status));
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);
}
printf("on new connection, status:%d\r\n", status);
}
int main()
{
printf("buliding tcp\n");
loop = uv_default_loop();
uv_tcp_t server;
uv_tcp_init(loop, &server);
printf("port%d\n", DEFAULT_PORT);
uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
printf("listening %d\n", DEFAULT_PORT);
int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection);
if (r)
{
fprintf(stderr, "Listen error %s\n", uv_strerror(r));
return 1;
}
return uv_run(loop, UV_RUN_DEFAULT);
}
tcp client代码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libuv/uv.h"
using namespace std;
uv_loop_t *loop;
struct sockaddr_in req_addr;
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
buf->base = (char*) malloc(suggested_size);
buf->len = suggested_size;
}
void echo_read(uv_stream_t *server, ssize_t nread, const uv_buf_t *buf)
{
if (nread < 0) {
fprintf(stderr, "error echo_read");
return;
}
printf("response from server: %s\n", buf->base);
}
void on_write_end(uv_write_t *req, int status)
{
if (status < 0) {
fprintf(stderr, "error on_write_end");
return;
}
uv_read_start(req->handle, alloc_buffer, echo_read);
}
//连接tcp server
void on_connect(uv_connect_t *req, int status)
{
if (status < 0) {
fprintf(stderr, "On_connection error: %d\n", status);
return;
}
//输入想要发送的数据
char buffer[100];
cin >> buffer;
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
buf.len = strlen(buffer);
buf.base = buffer;
uv_stream_t* tcp = req->handle;
uv_write_t write_req;
int buf_count = 1;
uv_write(&write_req, tcp, &buf, buf_count, on_write_end);
}
int main(void)
{
loop = uv_default_loop();
uv_tcp_t client;
uv_tcp_init(loop, &client);
//服务器端ip和port
uv_ip4_addr("0.0.0.0", 8000, &req_addr);
uv_connect_t connect_req;
//与server建立连接
uv_tcp_connect(&connect_req, &client, (const struct sockaddr*)&req_addr, on_connect);
return uv_run(loop, UV_RUN_DEFAULT);
}
五.编写udp代码
udp-server端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "libuv/uv.h"
#define DEFAULT_PORT 8001
uv_loop_t *loop;
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
buf->base = (char*) malloc(suggested_size);
buf->len = suggested_size;
}
void on_send(uv_udp_send_t *req, int status)
{
if (status < 0) {
fprintf(stderr, "Send error %s\n", uv_strerror(status));
}
}
void on_recv(uv_udp_t *req, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags)
{
if (nread < 0) {
fprintf(stderr, "Read error %s\n", uv_err_name(nread));
uv_close((uv_handle_t*) req, NULL);
free(buf->base);
return;
}
char sender[17] = { 0 };
uv_ip4_name((const struct sockaddr_in*)addr, sender, 16);
printf("on_recv:%s, recv from:%s\r\n", buf->base, sender);
int i=0;
while(i < nread)
{
buf->base[i] = toupper(buf->base[i]);
i++;
}
uv_buf_t wrbuf = uv_buf_init(buf->base, nread);
uv_udp_send_t send_req;
uv_udp_t send_socket;
uv_udp_init(loop, &send_socket);
uv_udp_send(&send_req, &send_socket, &wrbuf, 1, addr, on_send);
if (buf->base)
{
free(buf->base);
}
uv_udp_recv_stop(req);
}
int main()
{
printf("buliding udp\n");
loop = uv_default_loop();
uv_udp_t recv_socket;
//server端接收数据
uv_udp_init(loop, &recv_socket);
struct sockaddr_in recv_addr;
uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &recv_addr);
uv_udp_bind(&recv_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR);
uv_udp_recv_start(&recv_socket, alloc_buffer, on_recv);
return uv_run(loop, UV_RUN_DEFAULT);
}
udp-client代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <iostream>
#include "libuv/uv.h"
using namespace std;
#define DEFAULT_PORT 8001
uv_loop_t *loop;
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
buf->base = (char*) malloc(suggested_size);
buf->len = suggested_size;
}
void on_recv(uv_udp_t *req, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags)
{
if (nread < 0) {
fprintf(stderr, "Read error %s\n", uv_err_name(nread));
uv_close((uv_handle_t*) req, NULL);
free(buf->base);
return;
}
//nread=0表示没有更多的数据可读
if(nread > 0)
{
printf("on_recv:%s,len:%d\r\n", buf->base, nread);
}
}
void on_send_end(uv_udp_send_t *req, int status)
{
if (status < 0) {
fprintf(stderr, "Send error %s\n", uv_strerror(status));
}
uv_udp_recv_start(req->handle, alloc_buffer, on_recv);
}
int main()
{
loop = uv_default_loop();
//输入想要发送的数据
char buffer[100];
cin >> buffer;
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
buf.len = strlen(buffer);
buf.base = buffer;
uv_udp_t send_socket;
uv_udp_init(loop, &send_socket);
uv_udp_send_t send_req;
struct sockaddr_in addr;
uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);
uv_udp_send(&send_req, &send_socket, &buf, 1, (const struct sockaddr *)&addr, on_send_end);
return uv_run(loop, UV_RUN_DEFAULT);
}
libuv也支持管道通信和unix domain socket,通过uv_pipe_t 结构体来实现tcp 本地socket通信,多用于本地进程通信。但是现在libuv的uv_pipe_t仅支持发送TCP sockets或者other pipes。
参考文献:
http://docs.libuv.org/en/v1.x/guide/processes.html#sending-file-descriptors-over-pipes