TCP服务器的演变过程:C++使用libevent库开发服务器程序

news2024/11/25 3:02:49

C++使用libevent库开发服务器程序

  • 一、引言
  • 二、libevent简介
  • 三、Libevent库的封装层级
    • 3.1、reactor对象封装struct event_base
    • 3.2、事件对象struct event
    • 3.3、struct bufferevent对象
    • 3.4、evconnlistener对象
    • 3.5、事件循环
    • 3.6、事件处理
  • 四、完整示例代码
  • 小结

一、引言

手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。

为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。

在上一章节介绍了如何使用epoll构建reactor网络模型开发高效的服务器,有了上一节的基础,本节将介绍使用开源库libevent进行开发服务器程序。

event_base_new()
evconnlistener_new_bind()
设置回调函数
evconnlistener_enable()
event_base_dispatch()
evconnlistener_accept()
bufferevent_socket_new()
设置读回调函数
bufferevent_read()
数据处理
bufferevent_write()
关闭连接
创建事件驱动
初始化事件库
创建监听器
设置回调函数
启用监听器
事件循环
接受客户端连接
创建读写缓冲事件
设置读回调函数
读取数据
数据处理
发送数据
关闭连接

二、libevent简介

libevent是一个事件通知库,封装了reactor。

libevent API 提供了一种机制,用于在文件描述符上发生特定事件或达到超时后执行回调函数。此外,libevent还支持由于信号或常规超时而导致的回调。

libevent 旨在替换在事件驱动的网络服务器中找到的事件循环。应用程序只需要调用event_dispatch(),然后动态添加或删除事件,而无需更改事件循环。

目前,该控件支持/dev/poll, kqueue(), event ports, POSIX select(), Windows select(), poll(), and epoll()。内部事件机制完全独立于公开的事件 API,并且 libevent 的简单更新可以提供新功能,而无需重新设计应用程序。因此,Libevent 允许可移植应用程序开发,并提供操作系统上可用的最具可扩展性的事件通知机制。libevent 还可用于多线程应用程序,方法是隔离每个event_base,以便只有单个线程访问它,或者锁定对单个共享event_base的访问。自由的在 Linux、*BSD、Mac OS X、Solaris、Windows 等设备上编译。

libevent 还为缓冲网络 IO 提供了一个复杂的框架,支持套接字、筛选器、速率限制、SSL、零副本文件传输和 IOCP。自由度包括对几种有用协议的支持,包括 DNS、HTTP 和最小的 RPC 框架。

libevent编译安装:

官网下载安装包并解压。进入解压目录执行:

wget https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
tar -zxvf libevent-2.1.12-stable.tar.gz 
cd libevent-2.1.12-stable
./configure 
make 
sudo make install

三、Libevent库的封装层级

3.1、reactor对象封装struct event_base

reactor对象封装为struct event_base;通过:

(1)event_base_new()构造对象。

(2)event_base_free()销毁对象。

3.2、事件对象struct event

事件对象通过struct event的结构体封装使用。

struct event {
	struct event_callback ev_evcallback;

	/* for managing timeouts */
	union {
		TAILQ_ENTRY(event) ev_next_with_common_timeout;
		int min_heap_idx;
	} ev_timeout_pos;
	evutil_socket_t ev_fd;

	struct event_base *ev_base;

	union {
		/* used for io events */
		struct {
			LIST_ENTRY (event) ev_io_next;
			struct timeval ev_timeout;
		} ev_io;

		/* used by signal events */
		struct {
			LIST_ENTRY (event) ev_signal_next;
			short ev_ncalls;
			/* Allows deletes in callback */
			short *ev_pncalls;
		} ev_signal;
	} ev_;

	short ev_events;
	short ev_res;		/* result passed to event callback */
	struct timeval ev_timeout;
};
变量含义
ev_evcallback回调函数。事件是异步处理的,需要回调函数。
min_heap_idx时间事件的最小堆的索引。
ev_fd定时事件的fd。
ev_base事件对象所属的reactor的对象。
ev_io网络事件关注的事情。
ev_signal信号事件关注的事情。
ev_timeout超时。
ev_timeout_pos和ev_fd定时任务处理的事情。
ev_events具体注册的事件。
ev_具体的信号。

通常,event对象可以自己处理IO。

(1)event_new():构建事件对象、绑定、事件回调。

(2)event_free():销毁事件对象。

bufferevent和evconnlistener对象只需要关注业务逻辑的处理,由libevent内部处理IO操作。

bufferevent是在event对象上面封装的缓冲区。

3.3、struct bufferevent对象

struct bufferevent中的重要成员变量:

变量含义
ev_base事件对象所属的reactor的对象。
be_opsbufferevent的具体操作。控制某个事件的打开、关闭、移除等,其中input是用户态读缓冲区,output是用户态写缓冲区
readcb读事件的回调函数
writecb注意不是写事件回调,而是低水平触发的回调函数。这是涉及到写失败时的处理,内部会处理写事件发送出去。通常不需要设置写回调函数。
errorcb所有错误事件的回调函数。被动关闭连接或其他异常的回调函数。
wm_read读水平线,里面分有高水平和低水平。低水平是指buffer中有多少数据就要触发回调,默认为0,即每次读事件都会触发回调;高水平是指缓冲区中达到多大的数据就要关闭读事件,即buffer数据比较多的时候不再处理读事件。
wm_write写水平线,写只有低水平没有高水平。低水平默认值是0,即用户态缓冲区为空时回调写回调函数。

struct bufferevent_ops中的重要成员变量:

变量含义
input用户态读缓冲区。
output用户态写缓冲区。

(1)bufferevent_socket_new():构建bufferevent对象。
(2)bufferevent_free():销毁bufferevent对象。

3.4、evconnlistener对象

evconnlistener是专门处理listenfd的对象,使我们不需要关注bind、listen、accept的具体操作。

struct evconnlistener_ops {
	int (*enable)(struct evconnlistener *);
	int (*disable)(struct evconnlistener *);
	void (*destroy)(struct evconnlistener *);
	void (*shutdown)(struct evconnlistener *);
	evutil_socket_t (*getfd)(struct evconnlistener *);
	struct event_base *(*getbase)(struct evconnlistener *);
};

struct evconnlistener {
	const struct evconnlistener_ops *ops;
	void *lock;
	evconnlistener_cb cb;
	evconnlistener_errorcb errorcb;
	void *user_data;
	unsigned flags;
	short refcnt;
	int accept4_flags;
	unsigned enabled : 1;
};

(1)evconnlistener_new():构建evconnlistener对象、绑定、事件回调。

(2)evconnlistener_free():销毁evconnlistener对象。

(3)evconnlistener_bind_new():创建listenfd、bind、listen、注册读事件。

3.5、事件循环

(1)事件循环:event_base_dispatch(),event_base_loop()。

(2)事件循环退出:event_base_loopexit(),event_base_break()。

3.6、事件处理

设置事件相对应的回调。

(1)如果是使用event对象,在event_new()会设置相对应的回调。

(2)如果IO由libevent处理,那么使用bufferevent_setcb()来设置回调。

void
bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb, bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb, void *cbarg)
{
	BEV_LOCK(bufev);

	bufev->readcb = readcb;
	bufev->writecb = writecb;
	bufev->errorcb = eventcb;

	bufev->cbarg = cbarg;
	BEV_UNLOCK(bufev);
}

四、完整示例代码

#include <event.h>
#include <event2/listener.h>
#include <event2/buffer.h>

#include <sys/socket.h>

#include <functional>
#include <cstring>
#include <stdlib.h>


#define SOCKET_LISTEN_PORT  9703
#define SOCKET_BACKLOG_NUM  128

class asyn_event
{
private:
    
    /* data */
    struct event_base *base;
    struct evconnlistener *listener;
public:
    asyn_event(/* args */);
    ~asyn_event();
    static void accept_cb(struct evconnlistener *listen,evutil_socket_t fd,struct sockaddr *sock,int socklen,void *arg);
    static void socket_event_callback(struct bufferevent *bev, short events, void *arg);
    static void socket_read_callback(struct bufferevent *bev, void *arg);
    void loop_run();
};


asyn_event::asyn_event(/* args */)
{
    base=event_base_new();
    struct sockaddr_in server={0};
    server.sin_family=AF_INET;
    server.sin_addr.s_addr=htonl(INADDR_ANY);
    server.sin_port=htons(SOCKET_LISTEN_PORT);

    listener=evconnlistener_new_bind(
        base,
        &asyn_event::accept_cb,
        base,
        LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE,
        SOCKET_BACKLOG_NUM,
        (struct sockaddr*)&server,
        sizeof(server)
    );
}

asyn_event::~asyn_event()
{
    // 销毁evconnlistener对象
	evconnlistener_free(listener);

	// 销毁事件对象
	event_base_free(base);
}

void asyn_event::accept_cb(struct evconnlistener *listen,evutil_socket_t fd,struct sockaddr *sock,int socklen,void *arg)
{
	struct event_base *base = (struct event_base *)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 bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
	
	// 注册读事件
	bufferevent_setcb(bev, socket_read_callback, NULL, socket_event_callback, NULL);//写事件回调一般为NULL
	bufferevent_enable(bev, EV_READ | EV_PERSIST);


}
void asyn_event::loop_run()
{
    // 事件循环
	event_base_dispatch(base);
}

// 处理连接断开
void asyn_event::socket_event_callback(struct bufferevent *bev, short events, void *arg)
{
	if (events &BEV_EVENT_EOF)//read=0
	{
		printf("connection closed\n");
	}
	else if (events & BEV_EVENT_ERROR)//strerro(errno)
	{
		printf("some other error\n");
	}
	else if (events &BEV_EVENT_TIMEOUT)
		printf("time out\n");

	bufferevent_free(bev);// close(fd)
}

// 读回调
void asyn_event::socket_read_callback(struct bufferevent *bev, void *arg)
{
    struct evbuffer *input = bufferevent_get_input(bev);
    struct evbuffer *output = bufferevent_get_output(bev);

    // 从输入缓冲区读取数据
    char buf[1024];
    size_t len;
    while ((len = evbuffer_remove(input, buf, sizeof(buf))) > 0) {
        //printf("Received: %.*s", (int)len, buf);
        evbuffer_add(output, buf, len);
    }
    
	//bufferevent_write(bev, reply, strlen(reply));
}

int main()
{
    asyn_event ev;
    ev.loop_run();
    return 0;
}

编译时要指定事件库,添加 -levent 参数。

gcc -o ev ev.c  -levent

运行时出现libevent-2.1.so.7。

error while loading shared libraries: libevent-2.1.so.7: cannot open shared object file: No such file or directory

产生原因:libevent动态库在默认安装时,存放的路径在/usr/local/lib下,不在系统的默认查找路径内。

解决办法有两个:

(1)将该路径放在系统查找路径内。这种方法仅永久有效。

sudo echo "/usr/local/lib" >> /etc/ld.so.conf
sudo ldconfig

(2)添加环境变量的方法,添加 export LD_LIBRARY_PATH=XXX。这种方法仅当前有效。

export LD_LIBRARY_PATH=/usr/local/lib/

小结

(1)有了libevent可以不使用IO函数。因为如果使用IO函数,既需要知道这些IO函数里面的系统调用返回值的含义。

(2)有了libevent就可以不清楚数据拷贝原理。

(3)有了libevent就可以不清楚网络原理以及网络编程流程。

(4)有了libevent只需要知道事件处理,IO操作完全交由libevent处理。

至此,我们实现了使用libevent库开发高并发的服务器程序,但是,这个服务器程序有些局限性,我们还要继续改善、优化。在改进之前,需要开发一个后台日志模块,这是服务器程序必须的,所有,下一个章节将开发一个高效的后台日志模块。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1400443.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于springboot的一个IT人才招聘网站系统源码+数据库+部署文档,公司可以发布岗位需求,求职者查找岗位并递交简历等

介绍 实现一个IT人才招聘系统&#xff0c;公司可以发布岗位需求&#xff0c;求职者查找岗位并递交简历等 启动 1. 主要技术版本 技术名称版本SpringBoot2.5.0MySQL8.0Redis6.2.0 2. 本地启动部署 2.1 数据库数据源部署 src/main/resources/application.yaml 配置文件&am…

C++入门学习(一)写一个helloworld

1、头文件 #include <iostream> using namespace std; 任何程序都需要这两句的&#xff0c;写上就好。 2、主文件 int main() {cout<<"Hello World!"<<endl;return 0; } 由于是int型数据&#xff0c;所以要返回一个值&#xff0c;即return0。…

如何在Mac上安装PHP环境

前置环境&#xff1a;HomeBrew # Homebrew 是 Mac 上最好的包管理器之一&#xff0c;可以用于安装各种开源软件。从 Terminal&#xff08;终端&#xff09;执行以下命令安装 Homebrew&#xff1a; /usr/bin/ruby -e $(curl -fsSL https://raw.githubusercontent.com/Homebrew/i…

保姆级最新版Kali虚拟机安装和汉化中文教程

Kali虚拟机简介 Kali虚拟机是一款基于Debian的Linux发行版虚拟机操作系统&#xff0c;专为安全渗透测试和数字取证而设计。该虚拟机预装了许多渗透测试软件&#xff0c;包括Metasploit、BurpSuite、sqlmap、nmap以及Cobalt Strike等&#xff0c;这些工具都是为了进行网络安全测…

Go后端开发 -- 反射reflect 结构体标签

Go后端开发 – 反射reflect && 结构体标签 文章目录 Go后端开发 -- 反射reflect && 结构体标签一、反射reflect1.编程语言中反射的概念2.interface 和反射3.变量内置的pair结构4.reflect的基本功能TypeOf和ValueOf5.从relfect.Value中获取接口interface的信息6…

初识HarmonyOS

文章目录 本章节目标一、 HarmonyOS简介初识HarmonyOSHarmonyOS系统定位HarmonyOS典型应用场景 二、HarmonyOS架构与安全1. HarmonyOS架构解析内核层系统服务层框架层应用层应用服务智能分发 2. HarmonyOS系统安全正确的人正确的设备正确地使用数据 三、HarmonyOS关键特性1. 硬…

M1 MacOS下安卓虚拟化的最佳方案

categories: [VM] tags: MacOS VM 写在前面 一直想在桌面环境虚拟化安卓app, 但是看网上的推荐一直感觉不合胃口, 不是要花钱就是有广告, 想着找找开源的实现, 后来发现还是 Google 自家的产品用着舒服. 安装与配置 brew install android-studio然后随便开一个项目, 选默认…

JavaWeb-Filter

一、概念 Filter&#xff1a;过滤器&#xff0c;JavaWeb三大组件&#xff08;Servlet&#xff0c;Filter&#xff0c;Listener&#xff09;之一。作用是把对资源的请求拦截下来&#xff0c;从而实现一些特殊的功能。一般完成一些通用的操作&#xff0c;比如&#xff1a;权限控…

[全连接神经网络]Transformer代餐,用MLP构建图像处理网络

一、MLP-Mixer 使用纯MLP处理图像信息&#xff0c;其原理类似vit&#xff0c;将图片进行分块(patch)后展平(fallten)&#xff0c;然后输入到MLP中。理论上MLP等价于1x1卷积&#xff0c;但实际上1x1卷积仅能结合通道信息而不能结合空间信息。根据结合的信息不同分为channel-mixi…

移动机器人规划 - 基于采样的路径搜索

0 预备知识 基于采样的规划器&#xff1a; &#xff08;1&#xff09;不要试图显示地构造C空间及其边界 &#xff08;2&#xff09;只需要简单的机器人配置是否发生碰撞 &#xff08;3&#xff09;利用简单的碰撞测试&#xff0c;充分了解空间 &#xff08;4&#xff09;碰撞检…

UKP3d的管道编辑

山西这家用户在使用UKP3d时&#xff0c;提出以下问题&#xff1a; 1、stp导入的模型怎么测量距离&#xff1b;另外需要把某一个点移动至原点坐标&#xff0c;这个怎么操作呢&#xff1f; 回复&#xff1a;dist&#xff08;主要是捕捉点&#xff0c;推荐使用&#xff08;开启精…

Cortex-M3/M4内核NVIC及HAL库函数详解(4):使用HAL库配置外部中断

0 工具准备 Keil uVision5 Cortex M3权威指南&#xff08;中文&#xff09; Cortex M3与M4权威指南 stm32f407的HAL库工程 STM32F4xx中文参考手册 1 使用HAL库配置外部中断 前面我们已经熟悉了有关内核部分的寄存器配置&#xff0c;接下来我们结合stm32f407的GPIO外设&#xf…

深耕文档型数据库12载,SequoiaDB再开源

1月15日&#xff0c;巨杉数据库举行SequoiaDB新特性及开源项目发布活动。本次活动回顾了巨杉数据库深耕JSON文档型数据库12年的发展历程与技术演进&#xff0c;全面解读了SequoiaDB包括在高可用、安全、实时、易用性四个方向的技术特性&#xff0c;宣布了2024年面向技术社区的开…

toolz,一个无敌的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个无敌的 Python 库 - toolz。 Github地址&#xff1a;https://github.com/pytoolz/toolz Python是一种多用途的编程语言&#xff0c;具备广泛的库和框架&#xff0c;以支持各种编程范式&#x…

SSD硬盘数据恢复工具哪个强?分享五款好用的SSD硬盘数据恢复软件

固态硬盘 (SSD) 是数据存储领域真正的奇迹。SSD 没有机械部件&#xff0c;具有类似激光的读写速度&#xff0c;在笔记本电脑、游戏机和超级计算机中非常受欢迎。 然而&#xff0c;数据删除问题仍然很突出。如果您不小心删除了 SSD 中的关键数据怎么办&#xff1f; 5 款最佳 SS…

Python 类变量和实例变量详解

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;变量分为类变量和实例变量两种类型&#xff0c;它们有着不同的作用范围和生命周期。理解这两种变量类型的区别对于面向对象编程非常重要。本文将详细介绍Python中的类变量和实例变量&…

SATA驱动中FIS命令处理(详细)流程附代码和协议解析

目录 一、简介二、命令处理详细流程2.1 总体过程总结2.2 内存布局2.2.1 具体内存分配规则2.2.2 具体命令填充2.2.3 命令触发流程2.2.4 其他注意事项 三、其他相关链接1、SATA模块之HBA卡开发总结&#xff08;一&#xff09;2、SATA信息传输FIS结构总结3、PCIe物理层总结-PCIE专…

Vue2移动端项目使用$router.go(-1)不生效问题记录

目录 1、this.$router.go(-1) 改成 this.$router.back() 2、存储 from.path&#xff0c;使用 this.$router.push 3、hash模式中使用h5新增的onhashchange事件做hack处理 4、this.$router.go(-1) 之前添加一个 replace 方法 问题背景 &#xff1a; 在 Vue2 的一个移动端开发…

快速入门:使用 Gemini Embeddings 和 Elasticsearch 进行向量搜索

Gemini 是 Google DeepMind 开发的多模态大语言模型家族&#xff0c;作为 LaMDA 和 PaLM 2 的后继者。由 Gemini Ultra、Gemini Pro 和 Gemini Nano 组成&#xff0c;于 2023 年 12 月 6 日发布&#xff0c;定位为 OpenAI 的竞争者 GPT-4。 本教程演示如何使用 Gemini API 创建…

万界星空科技免费MES/开源MES/功能齐全,支持低代码大屏

目前国内智能制造如火如荼&#xff0c;工厂信息化、数字化是大趋势。如果找到一个工厂&#xff0c;搞定一个老板&#xff0c;搞软件的朋友就能吃几年。 开源软件不失为一条路子&#xff0c;大量的服务商选择开源MES做出了低成本的项目&#xff0c;收入也还可以。 今天介绍Git…