libevent简介 这是一个事件驱动的库,libevent API提供了一种机制,该机制可在文件描述符上发生特定事件或达到超时后执行回调函数。此外,libevent还支持由于信号或定期超时产生的回调。
libevent旨在替换事件驱动的网络服务器中的事件循环。应用程序只需要调用event_dispatch(),然后动态添加或删除事件,而无需更改事件循环。
目前,libevent支持/dev/poll,kqueue,event ports,POSIX select,Windows select,poll和epoll。 内部事件机制完全独立于公开的事件API,libevent的简单更新就可以提供新功能,而无需重新设计应用程序。正因为这样,libevent允许便携式应用程序开发,并提供操作系统上可用的最具扩展性的事件通知机制。 Libevent也可以用于多线程应用程序,方法是隔离每个event_base,以便只有一个线程可以访问它,或者通过锁定对单个共享event_base的访问来实现。 Libevent应该在Linux,* BSD,Mac OS X,Solaris,Windows等平台上编译。
Libevent还提供了用于缓冲网络IO的复杂框架,并支持套接字,过滤器,速率限制,SSL,零拷贝文件传输和IOCP。 Libevent包括对几种有用协议的支持,包括DNS,HTTP和最小的RPC框架。
有关网络服务器事件通知机制的更多信息,请参见Dan Kegel的”The C10K problem “ 网页。
1 2 3 4 5 6 7 8 9 kqueue() 这是 FreeBSD(以及即将推出的 NetBSD)的推荐边缘触发轮询替代品。 epoll 这是 2.6 Linux 内核的推荐边缘触发轮询替换。 使用异步调用(例如 aio_write()) 启动 I/O,使用完成通知(例如信号或完成端口)来了解 I/O 何时完成。适用于网络和磁盘 I/O。 每个线程为多个客户端提供服务,并使用非阻塞 I/O 和就绪更改通知,这与“水平触发”就绪通知相反。它对编程错误的容忍度要低一些,因为如果你只错过了一个事件,该事件的连接就会永远卡住。 使用每个服务器线程为多个客户端提供服务,并使用异步 I/O,这在 Unix 中还没有流行起来,可能是因为很少有操作系统支持异步 I/O,也可能是因为它(如非阻塞 I/O)需要重新考虑你的应用程序。 为每个服务器线程提供一个客户端服务,缺点是每个客户端都使用整个堆栈帧,这会消耗内存。许多操作系统在处理超过几百个线程时也遇到了麻烦。
libevent:使用了经典的基于 select、poll、epoll、kqueue 等系统调用的事件循环实现,支持跨平台操作系统,包括 Linux、Unix、Windows 等。 libevent:提供了更多高级抽象的接口,包括事件优先级、定时器、信号处理等功能,比如可以用 bufferevent 进行高级的缓冲区管理,支持更多的网络协议层,比如 HTTP、DNS 等。
libev:是一个轻量级的事件循环库,底层实现采用了对特定平台的高效事件通知机制,比如使用 epoll 在 Linux 上。它的设计更加简洁,代码库更小,性能更高。 libev:更加专注于事件循环本身,提供了更简单的接口,没有像 libevent 那样的高级抽象,因此更加轻量级和灵活。
libevent 和 libev 对 window 支持比较差,由此产生了 libuv 库;libuv 基于 libev,在window 平台上更好的封装了 iocp;node.js 基于 libuv;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #include <errno.h> #include <unistd.h> #include <sys/socket.h> #include <string.h> #include <time.h> #include <ev.h> static void do_accept (struct ev_loop *reactor, ev_io *w, int events) { struct sockaddr_in addr; memset (&addr, 0 , sizeof (struct sockaddr_in)); socklen_t len = sizeof (addr); int clientfd = accept (w->fd, (struct sockaddr *) &addr, &len); if (clientfd != -1 ) close (clientfd); printf ("accept fd = %d\n" , clientfd); } int socket_listen (uint16_t port) { int listenfd = socket (AF_INET, SOCK_STREAM, 0 ); struct sockaddr_in addr; memset (&addr, 0 , sizeof (struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons (port); addr.sin_addr.s_addr = INADDR_ANY; if (bind (listenfd, (struct sockaddr *) &addr, sizeof (struct sockaddr_in)) == -1 ) return -1 ; if (listen (listenfd, 5 ) == -1 ) return -1 ; printf ("server start listening port:%d\n" , port); return listenfd; } static void do_timer (struct ev_loop *loop, ev_timer *timer_w, int e) { time_t now = time (NULL ); printf ("do_timer %s" , (char *) ctime (&now)); } int main () { struct ev_loop *loop = ev_loop_new (0 ); struct ev_timer t1; ev_timer_init (&t1, do_timer, 1 , 1 ); ev_timer_start (loop, &t1); int listenfd = socket_listen (8989 ); if (listenfd == -1 ) return -2 ; struct ev_io i1; ev_io_init (&i1, do_accept, listenfd, EV_READ); ev_io_start (loop, &i1); ev_run (loop, 0 ); ev_loop_destroy (loop); return 0 ; }
关键函数 event_base_new: 使用libevent 函数之前需要分配一个或者多个 event_base 结构体, 每个event_base结构体持有一个事件集合, 可以检测以确定哪个事件是激活的, event_base结构相当于epoll红黑树的树根节点, 每个event_base都有一种用于检测某种事件已经就绪的 “方法” event_new: typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg); struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, event_callback_fn cb, void *arg); event_new负责创建event结构指针, 同时指定对应的base(epfd), 还有对应的文件描述符, 事件, 以及回调函数和回调函数的参数。类似于我们使用epoll的时候定义了struct epoll_event ev并且对ev的两个字段进行了赋值, 但是此时尚未调用epoll_ctl对事件上树. event_add: int event_add(struct event *ev, const struct timeval *timeout); 将非未决态事件转为未决态, 相当于调用epoll_ctl函数(EPOLL_CTL_ADD), 开始监听事件是否产生, 相当于epoll的上树操作.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <netinet/in.h> #include "event2/event.h" void socket_read_cb (int fd, short events, void *arg) ;void socket_accept_cb (int fd, short events, void *arg) { struct sockaddr_in addr; socklen_t len = sizeof (addr); evutil_socket_t clientfd = accept (fd, (struct sockaddr *) &addr, &len); evutil_make_socket_nonblocking (clientfd); printf ("accept a client %d\n" , clientfd); struct event_base *base = (struct event_base *) arg; struct event *ev = event_new (NULL , -1 , 0 , NULL , NULL ); event_assign (ev, base, clientfd, EV_READ | EV_PERSIST, socket_read_cb, (void *) ev); event_add (ev, NULL ); } void socket_read_cb (int fd, short events, void *arg) { char msg[4096 ]; struct event *ev = (struct event *) arg; int len = read (fd, msg, sizeof (msg) - 1 ); if (len <= 0 ) { printf ("client fd:%d disconnect\n" , fd); event_free (ev); close (fd); return ; } msg[len] = '\0' ; printf ("recv the client msg: %s" , msg); char reply_msg[4096 ] = "recvieced msg: " ; strcat (reply_msg + strlen (reply_msg), msg); write (fd, reply_msg, strlen (reply_msg)); } int socket_listen (int port) { int errno_save; evutil_socket_t listenfd = socket (AF_INET, SOCK_STREAM, 0 ); if (listenfd == -1 ) return -1 ; evutil_make_listen_socket_reuseable (listenfd); struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0 ; sin.sin_port = htons (port); if (bind (listenfd, (struct sockaddr *) &sin, sizeof (sin)) < 0 ) { evutil_closesocket (listenfd); return -1 ; } if (listen (listenfd, 5 ) < 0 ) { evutil_closesocket (listenfd); return -1 ; } evutil_make_socket_nonblocking (listenfd); return listenfd; } int main (int argc, char **argv) { int listenfd = socket_listen (8080 ); if (listenfd == -1 ) { printf ("socket_listen error\n" ); return -1 ; } struct event_base *base = event_base_new (); struct event *ev_listen = event_new (base, listenfd, EV_READ | EV_PERSIST, socket_accept_cb, base); event_add (ev_listen, NULL ); event_base_dispatch (base); return 0 ; }
自带buffer的事件-bufferevent bufferevent实际上也是一个event, 只不过比普通的event高级一些, 它的内部有两个缓冲区, 以及一个文件描述符(网络套接字)。一个网络套接字有读和写两个缓冲区, bufferevent同样也带有两个缓冲区, 还有就是libevent事件驱动的核心回调函数 一个bufferevent对应两个缓冲区, 三个回调函数, 分别是写回调, 读回调和事件回调
读回调:当bufferevent将底层读缓冲区的数据读到自身的读缓冲区时触发读事件回调.
写回调:当bufferevent将自身写缓冲的数据写到底层写缓冲区的时候触发写事件回调, 由于数据最终是写入了内核的写缓冲区中, 应用程序已经无法控制, 这个事件对于应用程序来说基本没什么用, 只是通知功能.
事件回调:当bufferevent绑定的socket连接, 断开或者异常的时候触发事件回调.
构建bufferevent对象 struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options); bufferevent_socket_new 对已经存在socket创建bufferevent事件, 可用于后面讲到的连接监听器的回调函数中. options : bufferevent的选项 BEV_OPT_CLOSE_ON_FREE – 释放bufferevent自动关闭底层接口(当bufferevent被释放以后, 文件描述符也随之被close) BEV_OPT_THREADSAFE – 使bufferevent能够在多线程下是安全的
连接操作bufferevent_socket_connect int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *serv, int socklen); 该函数封装了底层的socket与connect接口, 通过调用此函数, 可以将bufferevent事件与通信的socket进行绑定, 参数如下: bev – 需要提前初始化的bufferevent事件 serv – 对端(一般指服务端)的ip地址, 端口, 协议的结构指针 socklen – 描述serv的长度 说明: 调用此函数以后, 通信的socket与bufferevent缓冲区做了绑定
设置bufferevent回调bufferevent_setcb 对bufferevent缓冲区的读写操作的事件设置回调函数, 当往缓冲区中写数据的时候会触发写回调函数, 当数据从socket的内核缓冲区读到bufferevent读缓冲区中的时候会触发读回调函数.readcb, writecb,eventcb分别对应了读回调, 写回调, 事件回调, cbarg代表回调函数的参数。 读回调函数的触发时机: - 当socket的内核socket读缓冲区中有数据的时候, bufferevent会将内核缓冲区中的数据读到自身的读缓冲区, 会触发bufferevent的读操作, 此时会调用bufferevent的读回调函数. 写回调函数的触发时机: - 当往bufferevent的写缓冲区写入数据的时候, bufferevent底层会把缓冲区中的数据写入到内核的socket的写缓冲区中, 此时会触发bufferevent的写回调函数, 最后由内核的驱动程序将数据发送出去. 事件(异常)回调函数的触发时机: - 客户端关闭连接或者是被信号终止进程会触发事件回调函数
写数据到写缓冲区bufferevent_write int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size); int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf); bufferevent_write是将data的数据写到bufferevent的写缓冲区,bufferevent_write_buffer 是将数据写到写缓冲区另外一个写法, 实际上bufferevent的内部的两个缓冲区结构就是struct evbuffer。 当我们调用bufferevent_write往写缓冲区写数据时,实际上是调用了evbuffer_add,在写入后libevent自动帮我们写到内核缓冲区,之后会触发写回调函数。 若这个evbuffer中没有一个 chain 可以写入数据,则需要根据写入的数据大小新申请一个 chain 挂到链表末尾,然后往这个chain中写数据,所以每个 chain 的 buffer 大小是不定的。
从读缓冲区读数据bufferevent_read size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size); int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf); bufferevent_read 是将bufferevent的读缓冲区数据读到data中, 同时将读到的数据从bufferevent的读缓冲清除。 bufferevent_read_buffer 将bufferevent读缓冲数据读到buf中, 接口的另外一种。 bufferevent_read()底层调用evbuffer_remove,这代表它按照指定长度去读,其又调用了evbuffer_copyout_from、evbuffer_drain
获取读写缓冲区bufferevent_get_input和bufferevent_get_output struct evbuffer *bufferevent_get_input(struct bufferevent *bufev) struct evbuffer *bufferevent_get_output(struct bufferevent *bufev) 获取bufferevent的读缓冲区和写缓冲区
分割字符读evbuffer_readln,固定长度读evbuffer_remove char *evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out, enum evbuffer_eol_style eol_style); int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen); 分割字符读evbuffer_readln 固定长度读evbuffer_remove
链接监听器-evconnlistener 链接监听器封装了底层的socket通信相关函数, 比如socket, bind, listen, accept这几个函数。链接监听器创建后实际上相当于调用了socket, bind, listen, 此时等待新的客户端连接到来, 如果有新的客户端连接, 那么内部先进行调用accept处理, 然后调用用户指定的回调函数
构建链接监听器evconnlistener_new_bind struct evconnlistener *evconnlistener_new_bind( struct event_base *base,evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen ); 是在当前没有套接字的情况下对链接监听器进行初始化, 看最后2个参数实际上就是bind使用的关键参数, backlog是listen函数的关键参数(略有不同的是, 如果backlog是-1, 那么监听器会自动选择一个合适的值, 如果填0, 那么监听器会认为listen函数已经被调用过了), ptr是回调函数的参数, cb是有新连接之后的回调函数, 但是注意这个回调函数触发的时候, 链接器已经处理好新连接了, 并将与新连接通信的描述符交给回调函数。 flags 需要参考几个值: LEV_OPT_LEAVE_SOCKETS_BLOCKING 文件描述符为阻塞的 LEV_OPT_CLOSE_ON_FREE 关闭时自动释放 LEV_OPT_REUSEABLE 端口复用 LEV_OPT_THREADSAFE 分配锁, 线程安全
struct evconnlistener *evconnlistener_new( struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd ); evconnlistener_new函数与前一个函数不同的地方在与后2个参数, 使用本函数时, 认为socket已经初始化好, 并且bind完成, 甚至也可以做完listen, 所以大多数时候, 我们都可以使用第一个函数。
accept的回调函数evconnlistener_cb typedef void (*evconnlistener_cb)(struct evconnlistener *evl, evutil_socket_t fd, struct sockaddr *cliaddr, int socklen, void *ptr); 回调函数fd参数是与客户端通信的描述符, 并非是等待连接的监听的那个描述符, 所以cliaddr对应的也是新连接的对端地址信息, 已经是accept处理好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 #include <netinet/in.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <event.h> #include <time.h> #include <signal.h> #include <event2/listener.h> #include <event2/bufferevent.h> #include <event2/buffer.h> void socket_read_callback (struct bufferevent *bev, void *arg) { struct evbuffer *evbuf = bufferevent_get_input (bev); char *msg = evbuffer_readln (evbuf, NULL , EVBUFFER_EOL_LF); if (!msg) return ; printf ("server read the data: %s\n" , msg); char reply[4096 ] = {0 }; sprintf (reply, "recvieced msg: %s\n" , msg); free (msg); bufferevent_write (bev, reply, strlen (reply)); } void stdio_callback (struct bufferevent *bev, void *arg) { struct evbuffer *evbuf = bufferevent_get_input (bev); char *msg = evbuffer_readln (evbuf, NULL , EVBUFFER_EOL_LF); if (!msg) return ; if (strcmp (msg, "quit" ) == 0 ) { printf ("safe exit!!!\n" ); event_base_loopbreak (arg); } printf ("stdio read the data: %s\n" , msg); } void socket_event_callback (struct bufferevent *bev, short events, void *arg) { if (events & BEV_EVENT_EOF) printf ("connection closed\n" ); else if (events & BEV_EVENT_ERROR) printf ("some other error\n" ); else if (events & BEV_EVENT_TIMEOUT) printf ("timeout\n" ); bufferevent_free (bev); } void listener_callback (struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sock, int socklen, void *arg) { char ip[32 ] = {0 }; evutil_inet_ntop (AF_INET, sock, ip, sizeof (ip) - 1 ); printf ("accept a client fd:%d ip:%s\n" , fd, ip); struct event_base *base = (struct event_base *) arg; struct bufferevent *bev = bufferevent_socket_new (base, fd, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb (bev, socket_read_callback, NULL , socket_event_callback, NULL ); bufferevent_enable (bev, EV_READ | EV_PERSIST); } static void do_timer (int fd, short events, void *arg) { struct event *timer = (struct event *) arg; time_t now = time (NULL ); printf ("do_timer %s" , (char *) ctime (&now)); } static void do_sig_int (int fd, short event, void *arg) { struct event *si = (struct event *) arg; event_del (si); printf ("do_sig_int SIGINT\n" ); } int main () { struct sockaddr_in sin; memset (&sin, 0 , sizeof (struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons (8080 ); struct event_base *base = event_base_new (); struct evconnlistener *listener = evconnlistener_new_bind (base, listener_callback, base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, 10 , (struct sockaddr *) &sin, sizeof (struct sockaddr_in)); struct bufferevent *ioev = bufferevent_socket_new (base, 0 , BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb (ioev, stdio_callback, NULL , NULL , base); bufferevent_enable (ioev, EV_READ | EV_PERSIST); struct event evtimer; struct timeval tv = {1 , 0 }; event_set (&evtimer, -1 , EV_PERSIST, do_timer, &evtimer); event_base_set (base, &evtimer); event_add (&evtimer, &tv); struct event evint; event_set (&evint, SIGINT, EV_SIGNAL, do_sig_int, &evint); event_base_set (base, &evint); event_add (&evint, NULL ); event_base_dispatch (base); evconnlistener_free (listener); event_base_free (base); return 0 ; }
evbuffer 是 libevent 底层实现的一种链式缓冲区,当我们使用bufferevent来管理事件时,就会从每个事件的 evbuffer 中读写数据。每个 evbuffer 实质是一个缓冲区链表,其中的每个元素为 struct evbuffer_chain。一个struct evbuffer中的关键成员定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 struct evbuffer { struct evbuffer_chain *first; struct evbuffer_chain *last; struct evbuffer_chain **last_with_datap; size_t total_len; ...... } struct evbuffer_chain { struct evbuffer_chain *next; size_t buffer_len; ev_misalign_t misalign; size_t off; int refcnt; unsigned char *buffer; };
HTTP服务器 TCP 三次握手 这是基本的知识,必须掌握,我把它写在另外一篇文章了,点击这里 查看。
TCP Server 这里需要掌握socket的一些知识,可以直接查看man手册,通过命令man -f socket找到对应的章节,然后man 2 socket查看即可,篇幅较长,点击这里 查看。
HTTP Server 有了上面的tcp socket编程基础后,我们可以尝试加入http协议部分,来完成对收到的http请求进行响应。 具体参考C++实现简单的HTTP Serve 。
Libevent Server
https://kukuruku.co/post/lightweight-http-server-in-less-than-40-lines-on-libevent-and-c-11/
libevent对socket相关函数进行了进一层的封装,加入了对http协议对处理,并结合本身的事件处理机制,可以快速的建立http服务器。
libevent比libev和boost.asio更好,因为它具有嵌入式HTTP服务器和用于缓冲区操作的抽象。 它还具有大量的辅助功能。 您可以自己编写一个简单的FSN(有限状态机)或通过其他方法来检查HTTP协议。 使用libevent时它已经全部存在。 您也可以进入较低级别,为HTTP编写自己的解析器,并在libevent上使用套接字执行工作。 我喜欢这个库的详细程度。 如果您想快速执行某项操作,将会发现使用高级语言的接口通常不那么灵活的。 如果有更严格的要求,则可以逐级逐步降低。 该库允许执行许多操作,例如:异步输入/输出,与网络配合使用,与计时器配合使用,rpc等。您还可以使用它来创建服务器端和客户端软件。
参考libevent-2.1.11 ,其中的http.c给出了大部分封装后的api,实际使用这些api的例子可以参考http_server.c文件,我根据其中的api做了一个最简单的服务器,接收到客户端的任何请求,均返回ok,其中的evhttp_set_cb是指定特定的uri,evhttp_set_gencb是指没有被cb过滤的uri均会被gencb处理,如果gencb也没有设置的话,就会返回404页面,查看源代码的http.c里面的evhttp_handle_request函数即可得知,可以看出,经过封装后的服务器代码看起来非常简洁,短短几十行就能达到之前的效果,其实效果还更好。
evhttp其实是bufferevent的http版本,而bufferevent是在之前的event上封装出来的,使用起来更加方便,通过buffevent_setcb就能设置好读写、异常事件的回调函数,里面已经实现了ev_read/ev_write/ev_error,后面只要通过bufferevent_enable/bufferevent_disable就可以将事件event_add起来,如果设置了定时器,就要使用bufferevent_settimeout,这里要特别注意ev_error异常事件的处理,比如连接失败,连接异常,连接超时,客户端主动关闭连接等都是需要在这个里面处理的,通过what参数来判断传进来异常的类型,可以看看bufferevent_event_cb函数指针原型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <evhttp.h> #include <string.h> namespace {const std::string addr = "127.0.0.1" ;const int port = 8080 ;} void common (evhttp_request *req, void *arg) { std::cout << "common uri: " << req->uri << std::endl; evhttp_send_reply (req, 200 , "OK" , NULL ); } void test (evhttp_request *req, void *arg) { std::cout << "test uri: " << req->uri << std::endl; evhttp_send_reply (req, 200 , "OK" , NULL ); } int main (int argc, char *argv[]) { event_init (); struct evhttp *server = evhttp_start (addr.c_str (), port); evhttp_set_cb (server, "/test" , test, nullptr ); evhttp_set_gencb (server, common, nullptr ); event_dispatch (); evhttp_free (server); return 0 ; }
使用event_init函数初始化库的全局对象current_base。你只能将此功能用于单线程处理。为了执行多线程操作,应该为每个线程创建一个单独的对象,http_server.c中的例子就可以。
最后可以使用event_dispatch函数开始事件处理的循环。此函数用于在一个线程中使用全局对象。
可以结合C++11的一些语法将上述代码进行优化,利用智能指针自动释放内存,在一个函数里面定义另外一个函数指针,更加的精简代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <memory> #include <cstdint> #include <iostream> #include <evhttp.h> int main () { if (!event_init ()) { std::cerr << "Failed to init libevent." << std::endl; return -1 ; } char const SrvAddress[] = "127.0.0.1" ; std::uint16_t SrvPort = 8080 ; std::unique_ptr<evhttp, decltype (&evhttp_free) > Server (evhttp_start(SrvAddress, SrvPort), &evhttp_free) ; if (!Server) { std::cerr << "Failed to init http server." << std::endl; return -1 ; } void (*OnReq)(evhttp_request * req, void *) = [](evhttp_request * req, void *) { auto *OutBuf = evhttp_request_get_output_buffer (req); if (!OutBuf) return ; evbuffer_add_printf (OutBuf, "<html><body><center><h1>Hello World!</h1></center></body></html>" ); evhttp_send_reply (req, HTTP_OK, "" , OutBuf); }; evhttp_set_gencb (Server.get (), OnReq, nullptr ); if (event_dispatch () == -1 ) { std::cerr << "Failed to run messahe loop." << std::endl; return -1 ; } return 0 ; }
多线程的版本需要调用更加底层的api,首先得自己创建event_base,不能再用公共的current_base,然后建立evhttp对象,设置好回调函数再进行事件循环,注意其中的异常处理,销毁机制同样采用智能指针即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <stdexcept> #include <iostream> #include <memory> #include <chrono> #include <thread> #include <cstdint> #include <vector> #include <evhttp.h> int main () { char const SrvAddress[] = "127.0.0.1" ; std::uint16_t const SrvPort = 5555 ; int const SrvThreadCount = 4 ; try { void (*OnRequest)(evhttp_request *, void *) = [] (evhttp_request *req, void *) { auto *OutBuf = evhttp_request_get_output_buffer (req); if (!OutBuf) return ; evbuffer_add_printf (OutBuf, "<html><body><center><h1>Hello World!</h1></center></body></html>" ); evhttp_send_reply (req, HTTP_OK, "" , OutBuf); }; std::exception_ptr InitExcept; bool volatile IsRun = true ; evutil_socket_t Socket = -1 ; auto ThreadFunc = [&] () { try { std::unique_ptr<event_base, decltype (&event_base_free)> EventBase (event_base_new (), &event_base_free); if (!EventBase) throw std::runtime_error ("Failed to create new base_event." ); std::unique_ptr<evhttp, decltype (&evhttp_free) > EvHttp (evhttp_new(EventBase.get()), &evhttp_free) ; if (!EvHttp) throw std::runtime_error ("Failed to create new evhttp." ); evhttp_set_gencb (EvHttp.get (), OnRequest, nullptr ); if (Socket == -1 ) { auto *BoundSock = evhttp_bind_socket_with_handle (EvHttp.get (), SrvAddress, SrvPort); if (!BoundSock) throw std::runtime_error ("Failed to bind server socket." ); if ((Socket = evhttp_bound_socket_get_fd (BoundSock)) == -1 ) throw std::runtime_error ("Failed to get server socket for next instance." ); } else { if (evhttp_accept_socket (EvHttp.get (), Socket) == -1 ) throw std::runtime_error ("Failed to bind server socket for new instance." ); } for ( ; IsRun ; ) { event_base_loop (EventBase.get (), EVLOOP_NONBLOCK); std::this_thread::sleep_for (std::chrono::milliseconds (10 )); } } catch (...) { InitExcept = std::current_exception (); } }; auto ThreadDeleter = [&] (std::thread *t) { IsRun = false ; t->join (); delete t; }; typedef std::unique_ptr<std::thread, decltype (ThreadDeleter)> ThreadPtr; typedef std::vector<ThreadPtr> ThreadPool; ThreadPool Threads; for (int i = 0 ; i < SrvThreadCount ; ++i) { ThreadPtr Thread (new std::thread(ThreadFunc), ThreadDeleter) ; std::this_thread::sleep_for (std::chrono::milliseconds (500 )); if (InitExcept != std::exception_ptr ()) { IsRun = false ; std::rethrow_exception (InitExcept); } Threads.push_back (std::move (Thread)); } std::cout << "Press Enter fot quit." << std::endl; std::cin.get (); IsRun = false ; } catch (std::exception const &e) { std::cerr << "Error: " << e.what () << std::endl; } return 0 ; }
HTTPS服务器
https://segmentfault.com/a/1190000016855991 https://cheapsslsecurity.com/blog/http-vs-https-do-you-really-need-https/
原理上很简单,在原来http的基础上,加入ssl通道,所以在原来http三次握手的基础上还要增加ssl握手协议,建立连接的时间也会变长,可以用tcpdump tcp port加端口查看实际的报文,发现多了一次握手的过程,也可以tcpdump -i any -w file.cap tcp port 加端口打包后结合wireshark进行分析,在实际编程的过程中,accept后会生成一个新的套接字fd,然后新建ssl套接字并绑定之前的fd,利用ssl套接字进行握手监听,后续通信都在ssl套接字上进行。 具体步骤可分为:1、初始化OpenSSL;2、创建CTX;3、创建SSL套接字;4、完成SSL握手;5、 数据传输;6、会话结束;
其中所有的数据传输采用SSL_read、SSL_write接口。 在libevent中,ssl相关的接口均被封装起来了,使得使用openssl的方法更加便捷,但是初始化openssl、释放openssl资源的一些操作还是需要自己动手,见不到SSL_read、SSL_write接口了,全部在bufferevent相关的函数里面,比如bufferevent_openssl_socket_new会生成一个ssl接口的bufferevent对象,如果不使用ssl,初始化bufferevent对象时使用bufferevent_socket_new即可,后续基于该对象bev进行读写操作即可,如bufferevent_write_buffer、bufferevent_read_buffer,可以看出后续有没有使用ssl的操作都是一样的。
初始化时需要用到https的证书和私钥,开发阶段可以自己生成,一般使用单向加密就行,首先生成RSA私钥(使用aes256加密):openssl genrsa -aes256 -out server.key 2048,再根据该私钥生成一个证书:openssl req -new -x509 -days 3650 -key server.key -out server.crt,由于这个证书是自制的,所以一般浏览器不认,调试的时候,通过高级选项让它信任即可,如果使用curl请求的话,加一个-k的参数即可。
关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) SSL_library_init (); ERR_load_crypto_strings (); SSL_load_error_strings (); OpenSSL_add_all_algorithms (); #endif ssl_ctx = SSL_CTX_new (SSLv23_method ()); if (!ssl_ctx) { err_openssl ("SSL_CTX_new" ); goto error; } if (!SSL_CTX_use_certificate_file (ctx, CERT_FILE, SSL_FILETYPE_PEM)) { err_openssl (stdout); goto error; } if (!SSL_CTX_use_PrivateKey_file (ctx, PKEY_FILE, SSL_FILETYPE_PEM)) { err_openssl (stdout); goto error; } if (!SSL_CTX_check_private_key (ctx)) { err_openssl (stdout); goto error; } base = event_base_new (); if (!base) { perror ("event_base_new()" ); goto error; } ssl = SSL_new (ssl_ctx); if (ssl == NULL ) { err_openssl ("SSL_new()" ); goto error; } bufferevent *bev = bufferevent_openssl_socket_new (base, -1 , ssl, BUFFEREVENT_SSL_CONNECTING, BEV_OPT_CLOSE_ON_FREE); bev = bufferevent_socket_new (base, -1 , 0 ); if (bev == NULL ) { fprintf (stderr, "bufferevent_openssl_socket_new() failed\n" ); goto error; } bufferevent_setcb (bev, evhttp_read_cb, evhttp_write_cb, evhttp_error_cb, evcon);evcon->bufev = bev;
如果结合libevent库http.c例子来看的话,只要看evhttp_start函数就行了,服务器大概的运行流程为:
建立服务器对象:创建 evhttp 对象,用于表示一个 HTTP 服务器。
绑定地址和端口:使用 evhttp_bind_socket 绑定服务器的地址和端口,以便接收客户端的连接请求。
处理请求:
当有客户端连接到服务器时,会触发 accept_socket_cb 事件回调。
在回调函数中,通过 evhttp_get_request 获取客户端的 HTTP 请求。
该函数会生成一个新的连接对象 evhttp_connection,用于表示与客户端的连接。
接着通过 evhttp_associate_new_request_with_connection 创建一个基于该连接的请求对象 evhttp_request。
读取请求数据:
调用 evhttp_start_read 开始读取客户端发送的请求数据。
进入 evhttp_read_cb 回调函数,根据 evcon->state 的状态机进行处理。
首先读取请求行(比如 GET / HTTP/1.0),然后读取请求头部信息,将其存储到 req->input_headers 中。
当请求头部信息读取完毕后,进入 evhttp_connection_done 函数,最终调用请求的回调函数来处理请求。
处理请求:
调用用户设置的回调函数(通过 evhttp_set_gencb 或 evhttp_set_cb 设置),处理具体的业务逻辑。
如果没有设置对应 URI 的回调函数,则返回 404 Not Found。
处理完业务逻辑后,发送响应给客户端,完成一个请求-响应循环。
感觉比较绕,一般可以结合gdb来实际看看代码的流动过程,打一些关键的断点,然后不断的按continue,再用浏览器发请求,此时可以把运行到的断点按先后顺序打印出来了。
目前只能实现一种http或者https,没办法同时兼容两种协议,可能要用到重定向?目前看ngix的源码是这么写的,不知道libevent该怎么改,后面有想到再更新吧!