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文件夹

image-20240414125346381

image-20240414115435365

特点

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)注册事件优先级;

框架

  1. 创建event_base
#include <event2/event.h>
struct event_base *event_base_new(void);
struct event_base *base = event_base_new();
  1. 创建事件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 : 回调函数

  1. 把事件放到base上面
int event_add(struct event *ev, const struct timeval *tv)
  1. 循环监听事件满足
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);

立即停止循环

  1. 释放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 _WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

	base = 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, accept
	listener = 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_wait
	event_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);

未决和非未决

未决: 有资格被处理, 但是还没有被处理

非未决: 没有资格被处理

image-20240415222945402

实际使用

#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();//获取支持的IO
	printf("Using Libevent with backend method %s.\n", event_base_get_method(base));//获取使用IO
	printf("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: 标志

主要使用的有两个

  1. LEV_OPT_CLOSE_ON_FRE, 释放bufferevent时候关闭这一个传输端口, 释放底层套接字
  2. 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+端口

实际使用

  • 服务器端
  1. 创建event_base
  2. 建立一个监听服务器evconnlistener_new_bind, 设置一个回调函数, 有连接的时候这一个回调函数会被调用, 获取fd
  3. 封装listner_cb, 在这一个函数里面和客户端通信
  4. 在连接回调函数里面使用获取的fd创建一个buffer_event, 使用bufferevent_socket_new
  5. 设置一下回调函数, bufferevent_setcb, 给bufferevent的read, write和event设置回调函数
  6. 开启读写缓冲区
  7. 初始化回调函数, 监听的事件满足的时候, 回调函数被调用, 在read_cb里面使用bufferevent_read
  8. 启动监听event_base_dispatch
  9. 释放
#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_base
	struct 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;
}
  • 客户端
  1. 获取一个base
  2. bufferevent_socket_new, 获取一个用于通信的bufferevent对象
  3. 使用bufferevent_socket_connect进行连接服务器
  4. 使用bufferevent_setcb()给这一个对象设置一个回调函数
  5. 读写缓冲区的使能以及失能
  6. 收发数据
  7. 释放资源
#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;

}
  • 27
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值