libevent 库使用(一)socket实例

本文介绍了一种简化网络编程的方法,使用libevent库管理socket连接。通过示例代码展示了如何创建事件集、监听器及处理回调函数。libevent库可有效提高开发效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在工作中,之前经常使用tcp通信,每次都要自己写tcp通信程序,管理,比较麻烦,使用socket、bind、listen、connect、send、recv等基础函数虽然能够从最底层管理维护socket通信,但是socket通信一般不会使用其太高深的功能的情况下,libevent库完全可以胜任这项工作。

下面,贴一段源码来简单介绍以下libevent的功能,代码是从公司项目代码中抽离出来的,可能不会编译成功,这里我只是和大家分享一下使用libevent库的一般流程和注意事项,如果大家想要深入了解这个库,建议到官网上查看其用法,不过都是英文文献,望成功!

#include "linuxCommon.h"             /* 此头文件包含了linux下通用的头文件*/
#include "event2/listener.h"         /* libevent 库头文件 */
#include "event2/bufferevent.h"      /* libevent 库头文件 */
#include "event2/event_compat.h"     /* libevent 库头文件 */
#include "event2/buffer.h"           /* libevent 库头文件 */

#define LIBEVENT_NET_PORT   (9000)     /* libevent 监听端口 */

static struct event_base *g_pLibEvtBase = NULL;           /* 定义全局eventbase */
static struct evconnlistener *pLibEvtListener = NULL;     /* 监听器 */

int main(int argc, char *argv[])
{
	ret = 0;
	struct evconnlistener *pEvtListener = NULL;
    struct sockaddr_in svraddr = {0};
	
	/* 创建事件集 */
	g_pLibEvtBase = event_base_new(); 
	if(g_pLibEvtBase == NULL)
	{
		perror("event_base_new failed; g_pLibEvtBase = %p\n", g_pLibEvtBase);
		ret = -1;
		goto EXIT;
	}

	svraddr.sin_family = AF_INET;
	svraddr.sin_port = htons(LIBEVENT_NET_PORT);
	svraddr.sin_addr.s_addr = htonl(INADDR_ANY);

	/* 创建监听器: 
	* libAccept: 回调函数
	* LEV_OPT_REUSEABLE: 套接字关闭,可复用端口
	* LEV_OPT_CLOSE_ON_FREE:释放监听器时应关闭套接字
	* LEV_OPT_THREADSAFE:监听器应当被锁定,保证多线程安全
	* SESSION_MAX:rd模块支持的最大并发链接数量
	*/
	pLibEvtListener = evconnlistener_new_bind(g_pLibEvtBase, libAccept, \
		NULL, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE | LEV_OPT_THREADSAFE, SESSION_MAX, \
		(struct sockaddr*)&svraddr, sizeof(struct sockaddr_in));
	if(pLibEvtListener == NULL)
	{
		perror("evconnlistener_new_bind err; port = %d \n", LIBEVENT_NET_PORT);
		ret = -1;
		goto EXIT;
	}

	printf("lib listener init ok. port = %d\n", LIBEVENT_NET_PORT);

	ret = event_base_dispatch(g_pLibEvtBase); 
	if(ret != 0)
	{
		perror("event_base_dispatch failed! ret = %d\n", ret);
		ret = -1;
		goto EXIT;
	}

EXIT:	
	return ret;
}

此主程序中有几个函数需要重点介绍一下:

  1. event_base_new
    函数原型:struct event_base *event_base_new(void);
    函数说明:event_base_new()函数分配并且返回一个新的具有默认设置的event_base。函数会检测环境变量,返回一个到event_base的指针。如果发生错误,则返回NULL。
  2. 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);
    函数说明:这个函数会将监听器与事件集绑定,若有socket连接介入时,就会进入到其回调函数中,至于其他的参数,这里就不一一介绍了。
  3. event_base_dispatch
    函数原型:int event_base_dispatch(struct event_base *event_base)
    函数说明:该函数会使得之前定义的eventbase运行起来,如果查看源码的话,可以看出,实际该函数调用了event_base_loop函数。至于event_base_loop的作用, 这里就不介绍了,简而言之,event_base_dispatch函数会使之前定义好的环境运行起来。
  4. libAccept:这个是用户自定义的libevent回调函数

下面我们来看libAccept这个回调函数:

/**
 * @fn      libAccept
 * @brief   lib event accept回调, 表示建立的连接
 * @param[in]   pEvtListener : listener句柄
 *              fd: 网络套接字
 *              pSockAddr: 
 *              socklen:
 *              pUserData: 私有数据
 * @param[out]
 * @retval
 */
static void libAccept(
    struct evconnlistener *pEvtListener, 
    evutil_socket_t fd, 
    struct sockaddr *pSockAddr, 
    int socklen, 
    void *pUserData)
{
    int ret = 0;
    int sessionIdx = -1;
    struct bufferevent *pBuffEvt = NULL; 
    struct sockaddr_in clientAddr = {0};
    unsigned int len = sizeof(clientAddr);
    int fdOn = 1;
    int fdKeepIdle = 60;
    int fdKeepInterval = 20;
    int fdKeepCount = 3;

    /* 验证fd的合法性 */
	if(fd < 0)
	{
		perror("fd err.\n");
		ret = -1;
		goto EXIT;
	}

    /**
     * 添加fd的属性 
     * SO_KEEPALIVE:   发送保活包
     * TCP_KEEPIDLE:   空闲时间
     * TCP_KEEPINTVL:  发送间隔时间
     * TCP_KEEPCNT:    包的数量
     */
    setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &fdOn, sizeof(fdOn));
    setsockopt(fd, SOL_SOCKET, TCP_KEEPIDLE, (void*)&fdKeepIdle, sizeof(fdKeepIdle));
    setsockopt(fd, SOL_SOCKET, TCP_KEEPINTVL, (void*)&fdKeepInterval, sizeof(fdKeepInterval));
    setsockopt(fd, SOL_SOCKET, TCP_KEEPCNT, (void*)&fdKeepCount, sizeof(fdKeepCount));

    /**
     * 通过fd构建新的buffevent 
     * BEV_OPT_CLOSE_ON_FREE:释放bufferevent时关闭底层传输端口.
     * 这将关闭底层套接字,释放底层bufferevent等.
     */
    pBuffEvt = bufferevent_socket_new(g_pLibEvtBase, fd, BEV_OPT_CLOSE_ON_FREE); 
    if(pBuffEvt == NULL)
    {
        perror("bufferevent_socket_new failed; fd = %d g_pLibEvtBase = %p\n", fd, g_pLibEvtBase);
        ret = -1;
        goto EXIT;
    }

    /* 设置创建的 bufferevent 的回调函数 */
    bufferevent_setcb(pBuffEvt, libReadEvt,libWriteEvt, libErrorEtv, NULL);

    /* 使能回调函数, EV_PERSIST:当激活时不会被自动清除 */
    ret = bufferevent_enable(pBuffEvt, EV_READ | EV_PERSIST);
    if(ret != RD_OK)
    {
        perror("bufferevent_enable failed!\n");
        ret = -1;
        goto EXIT;
    }

    printf("libevent accept ok.\n");

EXIT:

    if(ret != -1)
    {
        perror("libevent  accept err.\n");
        
        if(pBuffEvt != NULL)
        {
            bufferevent_free(pBuffEvt);
            pBuffEvt = NULL;
            fd = -1;
        }

        if(fd > 0)
        {
            evutil_closesocket(fd);   /* 执行特定于平台的调用,close(fd) */
            fd = -1;
        }
    }

    return ;
}

此主程序中有几个函数需要重点介绍一下:

  1. bufferevent_socket_new
    函数说明:通过fd构建新的buffevent
  2. bufferevent_setcb
    函数说明:设置创建的 bufferevent 的回调函数,当socket收到数据时,会进入到libReadEvt回调函数中; 当要发送数据时,会进入到libWriteEvt回调函数中,当出错时,会进入到libErrorEtv回到函数中。
  3. bufferevent_enable
    函数说明:使能回调函数

接下来就要看具体的用户层回调函数了。

/**
 * @fn      libReadEvt
 * @brief   lib event数据读取回调
 * @param[in]   pBev : bufferevent句柄
 *              pSessionID : 会话句柄
 * @param[out]
 * @return
 * @retval
 * @retval
 */
static void libReadEvt(struct bufferevent *pBev)
{ 
    int ret = 0;
	int recvLen = 0;
	char buffer[1024] = {0};
	
    recvLen = bufferevent_read(pBufev, buffer, sizeof(buffer));
	
EXIT:

    return;
}



/**
 * @fn      libWriteEvt
 * @brief   lib event数据发送回调
 * @param[in]   pBev : bufferevent句柄
 *              pSessionID : 会话句柄
 * @param[out]
 * @return
 * @retval
 * @retval
 */
static void libWriteEvt(struct bufferevent *pBev)
{
    int ret = 0;
    
    /* 入参检查 */
	if(pBev == NULL)
	{
		perror("libWriteEvt param error; pBev  = %p\n", pBev);
	}

	/* 这里的写函数没有实现 */
    
EXIT:

    return;
}



/**
 * @fn      ilbErrorEtv
 * @brief   lib event连接异常状态回调
 * @param[in]   pBev : bufferevent句柄
 *              what : 状态
 *              pCtx : 私有数据
 * @param[out]
 * @return
 * @retval
 * @retval
 */
static void ilbErrorEtv(_IN struct bufferevent *pBev, _IN short what)
{
    int ret = 0;

    /* 入参检查 */
	if(pBev == NULL)
	{
		perror("libErrorEvt param error; pBev = %p\n", pBev);
		
	}

    if(what & BEV_EVENT_EOF)              /* clcient 断开连接 */
    {
        perror("connection closed.\n"); 
    }
    else if(what & BEV_EVENT_TIMEOUT)     /* 超时错误 */
    {
        perror("timeout.\n");
    
    }
    
EXIT:

    return;
}

总体来说,这个libevent 应用代码并不复杂,主要功能就是管理socket连接,可以释放开发人员的开发精力,毕竟,好多大公司的大型项目都是采用这个库的。

该代码在编译的时候,要连接libevent的库。
eg: gcc test.c -o test -levent

运行编译成功的可执行文件,通过socket测试工具,就可以测试改代码的实用性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我若成精

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值