本文主要是介绍Linux使用libevent协助socket编程进行网络通信----C语言实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
libevent库
这一篇是这一个课程的笔记
之后会发使用这一个库实现一个网页服务器的例程
这一个开源, 精简, 跨平台的库, 专注于网络通信
有两个版本比较稳定1.4和2.0
1.4比较简单适合用于学习
使用的话建议使用2.0
这两个版本的接口是不兼容的
libevent官网
libevent源码深度剖析-张亮_libevent源码深度剖析是c语言-CSDN博客
从官网下载以后可以使用./configure
检查安装环境以及生成Makefile, 之后使用make, 生成.o或者可执行文件, 最后sudo make install
把必要的资源拷贝到系统的指定目录
一般直接看readme文件
建立成功以后, 可以使用sample目录里面运行demo验证安装情况
➜ sample gcc hello-world.c -o hello -levent
编译的时候需要链接这一个库, 这一个库的实际的安装目录是/usr/local/lib文件夹
特点
1)事件驱动(event-driven),高性能;
2)轻量级,专注于网络,不如ACE那么臃肿庞大;
3)源代码相当精炼、易读;
4)跨平台,支持Windows、Linux、*BSD和Mac Os;
5)支持多种I/O多路复用技术, epoll、poll、dev/poll、select和kqueue等;
6)支持I/O,定时器和信号等事件;
7)注册事件优先级;
框架
- 创建event_base
#include <event2/event.h>
struct event_base *event_base_new(void);
struct event_base *base = event_base_new();
- 创建事件event
事件有两种, 常规的事件event, 以及bufferevent
struct event *event_new(struct event_base* base, evutil_socket_t fd, short what,event_callback_fn cd, void *arg);
struct bufferevent * bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options);
what: EV_READ
EV_WRITE
EV_PERSIST 持续触发
cd : 回调函数
- 把事件放到base上面
int event_add(struct event *ev, const struct timeval *tv)
- 循环监听事件满足
int event_base_dispatch(struct event_base *event_base)
{return (event_base_loop(event_base, 0)); //调用event_base_loop()
}
event_new函数里面指定了参数EV_PERSIST才可以持续触发, 否则只触发一次, 之后跳出循环
通常使用EV_WRITE|EV_PERSIST, EV_READ|EV_PERSIST
int event_base_loopexit(struct event_base*base, const struct timeval*tv);
指定时间以后退出循环
int event_base_loopbreak(struct event_base*base);
立即停止循环
- 释放eventbase
void bufferevent_free(struct bufferevent *bev);
示例分析
这是一个事件异步通信模型, 这一个文件里的所有都是事件, 主要依赖回调函数
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#ifndef _WIN32
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
# include <arpa/inet.h>
# endif
#include <sys/socket.h>
#endif#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>static const char MESSAGE[] = "Hello, World!\n";static const int PORT = 9995;
//cb指的是call back回调函数
static void listener_cb(struct evconnlistener *, evutil_socket_t,struct sockaddr *, int socklen, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);int main(int argc, char **argv)
{struct event_base *base;struct evconnlistener *listener;//用于监听struct event *signal_event; //一个事件struct sockaddr_in sin; //socket结构
#ifdef _WIN32WSADATA wsa_data;WSAStartup(0x0201, &wsa_data);
#endifbase = event_base_new(); //获取一个底座if (!base) {fprintf(stderr, "Could not initialize libevent!\n");return 1;}memset(&sin, 0, sizeof(sin));sin.sin_family = AF_INET;sin.sin_port = htons(PORT); //这里的ip地址使用的是0, 也就是监听所有的ip//这一个相当于socket, bind, listen, acceptlistener = evconnlistener_new_bind(base, listener_cb, (void *)base,LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,(struct sockaddr*)&sin,sizeof(sin));if (!listener) {fprintf(stderr, "Could not create a listener!\n");return 1;}//获取另一个事件, 这是一个信号事件signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);if (!signal_event || event_add(signal_event, NULL)<0) {fprintf(stderr, "Could not create/add a signal event!\n");return 1;}//这一个这一个相当于while和epoll_waitevent_base_dispatch(base);//这三个函数很可能到不了evconnlistener_free(listener);event_free(signal_event);event_base_free(base);printf("done\n");return 0;
}static void
listener_cb(struct evconnlistener *listener, evutil_socket_t fd,struct sockaddr *sa, int socklen, void *user_data)
{struct event_base *base = user_data;struct bufferevent *bev;bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);if (!bev) {fprintf(stderr, "Error constructing bufferevent!");event_base_loopbreak(base);return;}bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);bufferevent_enable(bev, EV_WRITE);bufferevent_disable(bev, EV_READ);bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}static void
conn_writecb(struct bufferevent *bev, void *user_data)
{struct evbuffer *output = bufferevent_get_output(bev);if (evbuffer_get_length(output) == 0) {printf("flushed answer\n");bufferevent_free(bev);}
}static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{if (events & BEV_EVENT_EOF) {printf("Connection closed.\n");} else if (events & BEV_EVENT_ERROR) {printf("Got an error on the connection: %s\n",strerror(errno));/*XXX win32*/}/* None of the other events can happen here, since we haven't enabled* timeouts */bufferevent_free(bev);
}static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{struct event_base *base = user_data;struct timeval delay = { 2, 0 };//这是一个信号的回调函数printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");event_base_loopexit(base, &delay);
}
常用函数
event_base_new创建base
#include <event2/event.h>
struct event_base *event_base_new(void);
struct event_base *base = event_base_new();
event_base_free销毁base
void event_base_free(struct event_base *base);
event_base_dispatch开启循环
int event_base_dispatch(struct event_base *event_base);
event_new创建事件
struct event *event_new(struct event_base*base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg);
在这一个事件base里面加入一个事件
base: 使用的base
fd: 监听的文件描述符
what: 监听的这一个文件的什么事件(读写异常)
- 常用
读事件EV_READ
写事件EV_ERITE
持续触发EV_PERSIST, 一般是配合event_base_dispatch函数使用
#define EV_TIMEOUT 0x01 //这一个废弃了 #define EV_READ 0x02 #define EV_WRITE 0x04 #define EV_SIGNAL 0x08 #define EV_PERSIST 0x10 #define EV_ET 0x20
cb: 回调函数
typedef void(*event_callback_fn)(evutil_socket_t fd, short, void *)
evutil_socket_t实际就是一个int类型, short是上面的what和arg
arg: 这一个函数的参数
返回值是成功创建的事件对象
event_add添加事件
int event_add(struct event *ev, const struct timeval *tv);
ev: event_new函数的返回值
tv: 时间, NULL这一个不会超时, 为非零的时候, 这一个事件没有触发也会被回调
不要设置 tv 为希望超时事件执行的时间。如果在 2010 年 1 月 1 日设置 “tv- >tv_sec=time(NULL)+10;”,超时事件将会等待40年,而不是10秒
struct timeval delay = { 2, 0 };
返回值 : 成功0, 失败-1
event_free销毁事件
void event_free(struct event *event);
event_del事件移出base
int event_del(struct event *ev);
未决和非未决
未决: 有资格被处理, 但是还没有被处理
非未决: 没有资格被处理
实际使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <event2/event.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>void write_cb(evutil_socket_t fd, short what, void *arg){char buf[1024];static int num = 0;sprintf(buf, "hello world-%d\n", num++);write(fd, buf, strlen(buf)+1);sleep(1);
}int main(void){int fd = open("myfifo", O_WRONLY | O_NONBLOCK);if(fd == -1){perror("open error");exit(1);}struct event_base *base = NULL;base = event_base_new();struct event *event = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL); event_add(event, NULL);event_base_dispatch(base);event_free(event);event_base_free(base);close(fd);return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <event2/event.h>
#include "fcntl.h"void read_cb(evutil_socket_t fd, short what, void *arg){char buf[1024];int len = read(fd, buf, sizeof(buf));printf("read event: %s \n", what & EV_READ ? "Yse" :"No" );printf("data len = %d, buf = %s\n", len, buf);sleep(1);
}int main(void){unlink("myfifo");mkfifo("myfifo", 0664);int fd = open("myfifo", O_RDONLY | O_NONBLOCK);if(fd == -1){perror("open error");exit(1);}struct event_base*base;base = event_base_new();struct event* ev = NULL;ev = event_new(base, fd, EV_READ| EV_PERSIST, read_cb, NULL);//添加事件,不加时间限制event_add(ev, NULL);//开始循环event_base_dispatch(base);event_free(ev);event_base_free(base);close(fd);return 0;}
其他函数
const char **event_get_supported_methods(void);
查看这一个系统支持的多路IO, epoll, poll, select之类的
const char * event_base_get_method(const struct event_base * base);
查看现在使用的IO
int event_reinit(struct event_base *base);
查看fork以后使用的这一个重新初始化这一个base
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <event2/event.h>int main(void){struct event_base *base;base = event_base_new();const char **buf;if(!base){printf("Could not initialize libevent!\n");return 1;}buf = event_get_supported_methods();//获取支持的IOprintf("Using Libevent with backend method %s.\n", event_base_get_method(base));//获取使用IOprintf("Starting Libevent %s. Available methods are:\n", event_get_version());//获取版本for(int i = 0; buf[i] != NULL; i++){printf("%s\n", buf[i]);}
}
➜ 2024-4-14-libvent ./event Using Libevent with backend method epoll. Starting Libevent 2.1.8-stable. Available methods are: epoll poll select
带缓冲区的事件
需要使用头文件<event2/bufferevent.h>
bufferevent, 里面有两个buffer, 一个读一个写, 使用队列实现, 读走以后就没有了, 先进先出
读 : 有数据 --> 读回调函数 --> 使用bufferevent_read --> 把数据读出来
写: 使用bufferevent_write --> 向缓冲区写数据 --> 缓冲区有数据自动写出 --> 写完 --> 回调函数
在这里面就不能使用read和write函数了
实际使用的时候需要在读缓冲回调函数里面使用bufferevent_read读取数据, 读取以后处理数据, 之后写入
写缓冲的回调函数用处不大
bufferevent_socket_new
struct bufferevent *bufferevent_socket_new(struct event_base *base,evutil_socket_t fd,enum bufferevent_options options);
base 是 event_base,options 是表示 bufferevent 选项 (BEV_OPT_CLOSE_ON_FREE 等) 的位掩码
fd是一个可选的表示套接字的文件 描述符。如果想以后设置文件描述符,可以设置fd为-1。 成功时函数返回一个 bufferevent,失败则返回 NULL。
optiona:
- BEV_OPT_CLOSE_ON_FREE:释放 bufferevent 时关闭底层传输端口。这将关闭底 层套接字,释放底层 bufferevent 等。
主要使用的是这一个
- BEV_OPT_THREADSAFE:自动为 bufferevent 分配锁,这样就可以安全地在多个线 程中使用 bufferevent。
- BEV_OPT_DEFER_CALLBACKS:设置这个标志时,bufferevent 延迟所有回调,如 上所述。
- BEV_OPT_UNLOCK_CALLBACKS:默认情况下,如果设置 bufferevent 为线程安全 的,则 bufferevent 会在调用用户提供的回调时进行锁定。设置这个选项会让 libevent 在执行回调的时候不进行锁定。
bufferevent_free
void bufferevent_free(struct bufferevent *bev);
bufferevent_setcb
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg);
void bufferevent_getcb(struct bufferevent *bufev, bufferevent_data_cb *readcb_ptr, bufferevent_data_cb *writecb_ptr, bufferevent_event_cb *eventcb_ptr, void **cbarg_ptr);
这两个缓冲区的回调函数
readcb_ptr: 读缓冲回调
writecb: 写缓冲回调
eventcb: 事件回调, 用于处理状态以及异常
cbarg: 回调使用的参数
- 回调函数参数
events: 实际的事件
BEV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。
BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。
BEV_EVENT_ERROR : 操作时发生错误。关于错误的更多信息,请调用 EVUTIL_SOCKET_ERROR()。
BEV_EVENT_TIMEOUT:发生超时。
BEV_EVENT_EOF:遇到文件结束指示。
BEV_EVENT_CONNECTED:请求的连接过程已经完成
ctx : 传递的参数
bufferevent_read
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
bufferevent_write
int bufferevent_write(struct bufferevent *bufev,const void *data, size_t size);
bufferevent_enable
void bufferevent_enable(struct bufferevent *bufev, short events);
void bufferevent_disable(struct bufferevent *bufev, short events);
启用以及禁用缓冲区
可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。
默认的时候读是disable, 写是enable的
short bufferevent_get_enabled(struct bufferevent *bufev);
获取缓冲区状态
网络通信
流程
使用头文件<event2/listener.h>
- 客户端
socket, connect
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);
可以使用这一个函数进行封装使用
address, addrlen实际就是connect函数的参数2和参数3
- 服务器端
socket, bind, listen, accept
struct evconnlistener *evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd);//建立一个监听器, 这一个不多使用(了解)
struct evconnlistener *evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,const struct sockaddr *sa, int socklen);//主要使用
void evconnlistener_free(struct evconnlistener *lev);
cb: 回调函数
ptr : 回调使用的参数
flags: 标志
主要使用的有两个
- LEV_OPT_CLOSE_ON_FRE, 释放bufferevent时候关闭这一个传输端口, 释放底层套接字
- LEV_OPT_REUSABLE: 端口可以复用
backlog: listen的第二个参数, 传-1表示使用最大值
sa: 服务器的IP+Port, 这一个是服务器自己的地址结构
成功的话返回一个监听器
typedef void (*evconnlistener_cb)(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *arg);
这一个是他的回调函数, 会在客户端连接的时候调用这一个函数
listener: evconnlistener_new_bind函数的返回值
fd: 用于通信的文件描述符
address: 客户端的IP+端口
实际使用
- 服务器端
- 创建event_base
- 建立一个监听服务器evconnlistener_new_bind, 设置一个回调函数, 有连接的时候这一个回调函数会被调用, 获取fd
- 封装listner_cb, 在这一个函数里面和客户端通信
- 在连接回调函数里面使用获取的fd创建一个buffer_event, 使用bufferevent_socket_new
- 设置一下回调函数, bufferevent_setcb, 给bufferevent的read, write和event设置回调函数
- 开启读写缓冲区
- 初始化回调函数, 监听的事件满足的时候, 回调函数被调用, 在read_cb里面使用bufferevent_read
- 启动监听event_base_dispatch
- 释放
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <arpa/inet.h>
#include <ctype.h>void event_cb(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");}bufferevent_free(bev);printf("event_cb\n");
}
void read_cb(struct bufferevent *bev, void *arg){char buf[1024];int len = bufferevent_read(bev, buf, sizeof(buf));if(len <= 0){printf("read_cb: connection closed\n");bufferevent_free(bev);return;}buf[len] = '\0';printf("read_cb: %s\n", buf);for(int i = 0;i<len;i++){buf[i] = toupper(buf[i]);}bufferevent_write(bev, buf, len);
}void write_cb(struct bufferevent *bev, void *arg){printf("write_cb\n");
}
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,struct sockaddr *addr, int socklen, void *arg){char buf[1024];int len;printf("connect from %s:%d\n", inet_ntop(AF_INET, &((struct sockaddr_in*)addr)->sin_addr,buf, sizeof(buf)),ntohs(((struct sockaddr_in*)addr)->sin_port));struct bufferevent *bev = bufferevent_socket_new(evconnlistener_get_base(listener), fd, BEV_OPT_CLOSE_ON_FREE);if(bev == NULL){printf("bufferevent_socket_newi error");return;}bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);bufferevent_enable(bev, EV_READ);
}int main(void){struct sockaddr_in serv;memset(&serv, 0, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(9876);serv.sin_addr.s_addr = htonl(INADDR_ANY);//创建event_basestruct event_base *base = event_base_new();if(base == NULL){printf("event_base_new");return -1;}//创建监听器struct evconnlistener *listener = evconnlistener_new_bind(base, listener_cb, base, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,-1, (struct sockaddr*)&serv, sizeof(serv));if(listener == NULL){printf("evconnlistener_new_bind");return -1;}//启动事件循环event_base_dispatch(base);//释放资源evconnlistener_free(listener);event_base_free(base);return 0;
}
- 客户端
- 获取一个base
- bufferevent_socket_new, 获取一个用于通信的bufferevent对象
- 使用bufferevent_socket_connect进行连接服务器
- 使用bufferevent_setcb()给这一个对象设置一个回调函数
- 读写缓冲区的使能以及失能
- 收发数据
- 释放资源
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>void read_cb(struct bufferevent *bev, void *ctx){char buf[1024] = {0};int len = bufferevent_read(bev, buf, sizeof(buf)-1);if(len <= 0){fprintf(stderr, "read data error!\n");return;}printf("recv data: %s\n", buf);
}
void read_terminal(evutil_socket_t fd, short what, void *arg){char buf[1024] = {0};int len = read(fd, buf, sizeof(buf)-1);if(len <= 0){fprintf(stderr, "read data error!\n");return;}struct bufferevent *bev = (struct bufferevent *)arg;bufferevent_write(bev, buf, len);
}
void write_cb(struct bufferevent *bev, void *ctx){printf("write data\n");
}
void event_cb(struct bufferevent *bev, short what, void *ctx){if(what & BEV_EVENT_EOF){printf("connection closed\n");}else if(what & BEV_EVENT_ERROR){printf("some other error\n");}else if(what & BEV_EVENT_CONNECTED){printf("connected\n");return;}bufferevent_free(bev);
}
int main(void){struct event_base *base;struct bufferevent *bev;struct sockaddr_in serv;base = event_base_new();if(!base){fprintf(stderr, "Could not initialize libevent!\n");return 1;}int fd = socket(AF_INET, SOCK_STREAM, 0);memset(&serv, 0, sizeof(serv));bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);if(!bev){fprintf(stderr, "Could not create bufferevent!\n");return 1;}//连接服务器serv.sin_family = AF_INET;serv.sin_port = htons(9876);inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr);bufferevent_socket_connect(bev, (struct sockaddr *)&serv, sizeof(serv));bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);bufferevent_enable(bev, EV_READ|EV_WRITE);//监听标准输入struct event *ev = event_new(base, STDIN_FILENO, EV_READ|EV_PERSIST, read_terminal, bev);event_add(ev, NULL);event_base_dispatch(base);event_free(ev);event_base_free(base);bufferevent_free(bev);return 0;}
这篇关于Linux使用libevent协助socket编程进行网络通信----C语言实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!