使用libevent实现一个简单的tcp服务端
一、概述
1.特点:
1.事件驱动、高性能、轻量级、专注于网络
2.源代码精炼、易读
3.跨平台
4.支持多种I/O多路复用技术,如epoll 、poll 、select等
5.支持I/O和信号等事件
2.使用libevent 函数之前需要分配一个或者多个 event_base 结构体, 每个event_base结构体持有一个事件集合, 可以检测以确定哪个事件是激活的, event_base结构相当于epoll红黑树的树根节点, 每个event_base都有一种用于检测某种事件已经就绪的 “方法”(回调函数)
通常情况下可以通过event_base_new函数获得event_base结构。
3.相关函数
1 struct event_base *event_base_new(void); 函数说明: 获得event_base结构 参数说明: 无 返回值: 成功返回event_base结构体指针; 失败返回NULL; 2 void event_base_free(struct event_base *); 函数说明: 释放event_base指针 3 int event_reinit(struct event_base *base); 函数说明: 如果有子进程, 且子进程也要使用base, 则子进程需要对event_base重新初始化, 此时需要调用event_reinit函数. 函数参数: 由event_base_new返回的执行event_base结构的指针 返回值: 成功返回0, 失败返回-1 对于不同系统而言, event_base就是调用不同的多路IO接口去判断事件是否已经被激活, 对于linux系统而言, 核心调用的就是epoll, 同时支持poll和select. 查看libevent支持的后端的方法有哪些: const char **event_get_supported_methods(void); 函数说明: 获得当前系统(或者称为平台)支持的方法有哪些 参数: 无 返回值: 返回二维数组, 类似与main函数的第二个参数**argv. const char * event_base_get_method(const struct event_base *base); 函数说明: 获得当前base节点使用的多路io方法 函数参数: event_base结构的base指针. 返回值: 获得当前base节点使用的多路io方法的指针 libevent在地基打好之后, 需要等待事件的产生, 也就是等待事件被激活, 所以程序不能退出, 对于epoll来说, 我们需要自己控制循环, 而在libevent中也给我们提供了API接口, 类似while(1)的功能. int event_base_dispatch(struct event_base *base); // 函数说明: 进入循环等待事件 参数说明:由event_base_new函数返回的指向event_base结构的指针 调用该函数, 相当于没有设置标志位的event_base_loop。程序将会一直运行, 直到没有需要检测的事件了, 或者被结束循环的API终止。 int event_base_loopexit(struct event_base *base, const struct timeval *tv); int event_base_loopbreak(struct event_base *base); struct timeval { long tv_sec; long tv_usec; }; 两个函数的区别是如果正在执行激活事件的回调函数, 那么event_base_loopexit将在事件回调执行结束后终止循环(如果tv时间非NULL, 那么将等待tv设置的时间后立即结束循环), 而event_base_loopbreak会立即终止循环。 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, 还有对应的文件描述符, 事件, 以及回调函数和回调函数的参数。 参数说明: base: 对应的根节点--地基 fd: 要监听的文件描述符 events:要监听的事件 #define EV_TIMEOUT 0x01 //超时事件 #define EV_READ 0x02 //读事件 #define EV_WRITE 0x04 //写事件 #define EV_SIGNAL 0x08 //信号事件 #define EV_PERSIST 0x10 //周期性触发 #define EV_ET 0x20 //边缘触发, 如果底层模型支持设置 则有效, 若不支持则无效. 若要想设置持续的读事件则: EV_READ | EV_PERSIST cb 回调函数, 原型如下: typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg); 注意: 回调函数的参数就对应于event_new函数的fd, event和arg #define evsignal_new(b, x, cb, arg) \ event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg)) int event_add(struct event *ev, const struct timeval *timeout); 函数说明: 将非未决态事件转为未决态, 相当于调用epoll_ctl函数(EPOLL_CTL_ADD), 开始监听事件是否产生, 相当于epoll的上树操作. 参数说明: ev: 调用event_new创建的事件 timeout: 限时等待事件的产生, 也可以设置为NULL, 没有限时。 int event_del(struct event *ev); 函数说明: 将事件从未决态变为非未决态, 相当于epoll的下树(epoll_ctl调用 EPOLL_CTL_DEL操作)操作。 参数说明: ev指的是由event_new创建的事件. void event_free(struct event *ev); 函数说明: 释放由event_new申请的event节点。
二、示例代码
//编写libevent服务端 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <event2/event.h> struct event *connev = NULL; void readcb(evutil_socket_t fd,short events,void *arg){ int n; char buf[1024]; memset(buf,0x00,sizeof(buf)); n = read(fd,buf,sizeof(buf)); if(n<=0){ close(fd); //将通讯文件描述符对应的事件从base地基上删除 event_del(connev); }else{ write(fd,buf,n); } } //创建连接回调事件 void conncb(evutil_socket_t fd,short events ,void *arg){ struct event_base *base = (struct event_base*)arg; //接收新的客户端连接 int cfd = accept(fd,NULL,NULL); if(cfd>0){ //创建通信文件描述对应的事件并设置回调函数为readcb connev = event_new(base,cfd,EV_READ|EV_PERSIST,readcb,NULL); if(connev==NULL){ //退出循环 event_base_loopexit(base,NULL); } //将通信文件描述符对应的事件上event_base地基 event_add(connev,NULL); } } int main(int argc, char const *argv[]) { //1.创建socket int lfd = socket(AF_INET,SOCK_STREAM,0); //2.设置端口复用 int opt; setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //3.绑定 struct sockaddr_in serv; bzero(&serv,sizeof(serv)); serv.sin_addr.s_addr = htonl(INADDR_ANY); serv.sin_port = htons(8888); serv.sin_family = AF_INET; bind(lfd,(struct sockaddr *)&serv,sizeof(serv)); //4.监听 listen(lfd,128); //5.创建地基 struct event_base *base = event_base_new(); if(base==NULL){ perror("event_base_new error"); return -1; } //6.创建文件描述符对应的事件 struct event *ev = event_new(base,lfd,EV_READ|EV_PERSIST,conncb,base); if(ev==NULL){ perror("event_new error"); return -1; } //7.将新的事件节点上base地基 event_add(ev,NULL); //8.进行事件分发(进入事件循环等待) event_base_dispatch(base); //9.释放资源 event_base_free(base); event_free(ev); close(lfd); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探