在工作中,之前经常使用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;
}
此主程序中有几个函数需要重点介绍一下:
- event_base_new
函数原型:struct event_base *event_base_new(void);
函数说明:event_base_new()函数分配并且返回一个新的具有默认设置的event_base。函数会检测环境变量,返回一个到event_base的指针。如果发生错误,则返回NULL。 - 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连接介入时,就会进入到其回调函数中,至于其他的参数,这里就不一一介绍了。 - event_base_dispatch
函数原型:int event_base_dispatch(struct event_base *event_base)
函数说明:该函数会使得之前定义的eventbase运行起来,如果查看源码的话,可以看出,实际该函数调用了event_base_loop函数。至于event_base_loop的作用, 这里就不介绍了,简而言之,event_base_dispatch函数会使之前定义好的环境运行起来。 - 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 ;
}
此主程序中有几个函数需要重点介绍一下:
- bufferevent_socket_new
函数说明:通过fd构建新的buffevent - bufferevent_setcb
函数说明:设置创建的 bufferevent 的回调函数,当socket收到数据时,会进入到libReadEvt回调函数中; 当要发送数据时,会进入到libWriteEvt回调函数中,当出错时,会进入到libErrorEtv回到函数中。 - 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测试工具,就可以测试改代码的实用性。