题记

windows平台下对于服务器高并发的网络模型选型中,使用libevent是个不错的选择。 
本文的背景基于:国内博客对于libevent大多介绍linux实现,大多是c语言的实现,Windows平台下C++相对较少或者较旧。 
本文针对libevent从服务端、客户端两个角度实现了封装类,真正做到,我们在使用封装类时候,只需要关注业务逻辑的处理,不需要关注socket的通信的细节。

1. Libevent介绍

[维基百科]libevent是一个异步事件处理软件函式库,以BSD许可证发布。libevent提供了一组应用程序编程接口(API),让程序员可以设定某些事件发生时所执行的回调函数,也就是说,libevent可以用来取代网络服务器所使用的事件循环检查框架。由于可以省去对网络的处理,且拥有不错的效能,有些软件使用libevent作为网络底层的函式库,如:memcached、Tor。

[libevent官网介绍] 
libevent API提供了一种机制,用于在文件描述符上发生特定事件或达到超时后执行回调函数。此外,libevent还支持基于信号或常规超时的回调。 
libevent旨在替换在事件驱动的网络服务器中的事件循环机制。应用程序只需要调用event_dispatch()接口,然后动态添加或删除事件,而不必更改事件循环。 
目前,libevent支持 /dev/poll, kqueue, event ports, POSIX select, Windows select(), poll 和 epoll。 其内在的事件机制是完全独立于外在的事件API ,并且libevent的简单更新可以提供新的功能,而无需重新设计应用程序。因此: 
1)Libevent允许便携式应用程序开发,并提供操作系统上可用的最可扩展的事件通知机制。 
2)Libevent也可以用于多线程应用程序,通过隔离每个event_base,以便只有一个线程访问它,或通过锁定访问单个共享的event_base。 Libevent可以在Linux,* BSD,Mac OS X,Solaris,Windows等上编译。 
3)Libevent还为缓冲网络IO提供了复杂的框架,支持套接字,过滤器,速率限制,SSL,零拷贝文件传输和IOCP。 
4)Libevent包括对几个有用的协议的支持,包括DNS,HTTP和最小的RPC框架。

以下关于阻塞、非阻塞,同步、异步,大牛陈硕的经典回复。 
在处理 IO 的时候,阻塞和非阻塞都是同步 IO。 
只有使用了特殊的 API 才是异步 IO。 

2.为什么要使用封装好的网络库?

[陈硕]网络编程是什么?是熟练使用Sockets API吗?说实话,在实际项目里我只用过两次Sockets API,其他时候都是使用封装好的网络库。 
使用封装好的网络库如libevent, muduo网络库 目的之一就是想让日常的网络编程从Sockets API的琐碎细节中解脱出来,让程序员专注于业务逻辑,把时间用在刀刃上。 程序员的主要工作是在事件处理函数中实现业务逻辑,而不是和Sockets API较劲。 
陈硕认为网络编程也可以分为三个层次: 
1). 读过教程和文档 
2). 熟悉本系统TCP/IP协议栈的脾气 
3). 自己写过一个简单的TCP/IP stack

陈硕认为TCP网络编程有三个例子最值得学习研究,分别是echo、chat、proxy,都是长连接协议。 
Echo的作用:熟悉服务端被动接受新连接、收发数据、被动处理连接断开。每个连接是独立服务的,连接之间没有关联。在消息内容方面Echo有一些变种:比如做成一问一答的方式,收到的请求和发送响应的内容不一样,这时候要考虑打包与拆包格式的设计,进一步还可以写简单的HTTP服务。

3.Libevent通信核心

服务端核心步骤简化如下:

步骤1:设置sockfd为nonblocking;

步骤2:使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base;

步骤3:使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)将EV_READ/EV_WRITE对应的函数;

步骤4:使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启动read/write事件;

其中,在read_cb里面从input读取数据,处理完毕后塞到output中,会自动写入到sockfd; 
在write_cb里面(对于一个echo server来说,read_cb足够了) 
在error_cb里面处理遇到的错误。使用bufferevent_set_timeout(bev, struct timeval *READ, struct timeval *WRITE)来设置读写超时,在error_cb里面也可以处理超时。 
可以使用bev中libevent的API提取出event_base,sockfd,input/output等相关数据。 
客户端的操作步骤详见代码,提炼即可。

4.C++封装的libevent Echo类

分为:服务端YuLibeventServer类和客户端YuLibeventClient类。

//服务端核心代码如下参考地址:

#include "YuLibeventServer.h"
/*
**@author: laoyang360
**@date: 20161211
**@brief: The server of SimLibeventClient
*/ static int s_iBlockSize = 10;
#define MAX_LINE 1024
YuLibeventServer *YuLibeventServer::pThis = NULL; YuLibeventServer::YuLibeventServer()
{
pThis = this; //将this指针赋给pThis,使得回调函数能通过pThis指针访问本对象
m_pBase = NULL;
m_pListener = NULL;
m_pEvstop = NULL; } YuLibeventServer::~YuLibeventServer()
{ } /*
**@author: laoyang360
**@date: 20161211
**@param: evutil_socket_t fd
**@brief: 设置非阻塞,禁止Nagle算法。
*/
void YuLibeventServer::set_tcp_no_delay(evutil_socket_t fd)
{
int iOne = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&iOne, sizeof iOne);
} /*
**@author: laoyang360
**@date: 20161211
**@param: evutil_socket_t fd
**@brief: 等待接受客户端连接处理accept、一个新客户端连接上服务器了
*/
void YuLibeventServer::accept_conn_cb(evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sock, int socklen, void *arg)
{
printf("We got a new connection! Set up a bufferevent for it. accept a client %d\n", fd); event_base *base = evconnlistener_get_base(listener); //为这个客户端分配一个bufferevent
bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); set_tcp_no_delay(fd); bufferevent_setcb(bev, echo_read_cb, NULL, echo_event_cb, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE); } /*
**@author: laoyang360
**@date: 20161211
**@param: bufferevent *bev, void *arg
**@brief: echo_read_cb回调接口
*/
void YuLibeventServer::echo_read_cb(bufferevent *bev, void *arg)
{ char msg[MAX_LINE + 1] = {0};
int iLen = 0;
evutil_socket_t fd = bufferevent_getfd(bev);
while (iLen = bufferevent_read(bev, msg, sizeof(msg)-1 ), iLen > 0)
{
msg[iLen] = '\0';
printf("fd=%u, read len = %d\t read msg: %s\n", fd, iLen, msg);
int iRst = bufferevent_write(bev, msg, iLen);
if (-1 == iRst)
{
printf("[socket_write_cb]:error occur!\n");
}
} /*
char reply[] = "[server: i'm server, send 1111]";
printf("writecb: len = %d\n", 1 + strlen(reply));
int iRst = bufferevent_write(bev, reply, 1 + strlen(reply));
if (-1 == iRst)
{
printf("[socket_write_cb]:error occur!\n");
}
*/
/*This callback is invoked when there is data to read on bev */
//struct evbuffer *input = bufferevent_get_input(bev);
//struct evbuffer *output = bufferevent_get_output(bev);
/*把input buffer中的所有数据 拷贝到 output buffer*/
//evbuffer_add_buffer(output, input); } /*
**@author: laoyang360
**@date: 20161211
**@param: bufferevent *bev, void *arg
**@brief: socket_write_cb回调接口,暂时未使用
*/
void YuLibeventServer::socket_write_cb(bufferevent *bev, void *arg)
{
/*
char reply[] = "[server: i'm server, send 1111]";
printf("writecb: len = %d\n", 1 + strlen(reply));
int iRst = bufferevent_write(bev, reply, 1 + strlen(reply));
if (-1 == iRst)
{
printf("[socket_write_cb]:error occur!\n");
}
*/
} /*
**@author: laoyang360
**@date: 20161211
**@param: bufferevent *bev, short events, void *arg
**@brief: echo_event_cb事件处理或异常处理
*/
void YuLibeventServer::echo_event_cb(bufferevent *bev, short events, void *arg)
{
struct evbuffer *output = bufferevent_get_output(bev);
size_t remain = evbuffer_get_length(output); if (events & BEV_EVENT_TIMEOUT)
{
printf("Timed out\n"); //if bufferevent_set_timeouts() called.
}
else if (events & BEV_EVENT_EOF)
{
printf("connection closed, remain %d\n", remain);
}
else if (events & BEV_EVENT_ERROR)
{
printf("some other error, remain %d\n", remain);
}
//这将自动close套接字和free读写缓冲区
bufferevent_free(bev);
} /*
**@author: laoyang360
**@date: 20161211
**@param: bufferevent *bev, short events, void *arg
**@brief: signal_cb停止信号处理
*/
void YuLibeventServer::signal_cb(evutil_socket_t sig, short events, void *arg)
{
struct event_base *base = (event_base *)arg;
printf("exception: interrupt, stop now!\n"); event_base_loopexit(base, NULL);
} /*
**@author: laoyang360
**@date: 20161211
**@param: int port, 传入端口。
**@brief: libevent,socket初始化等
*/
void YuLibeventServer::init(int port)
{
WSADATA wsaData;
DWORD Ret;
if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
{
printf("WSAStartup failed with error %d\n", Ret);
exit(1);
} m_pBase = event_base_new();
if (NULL == m_pBase)
{
printf("couldn't open event base!\n");
exit(1);
} m_pEvstop = evsignal_new(m_pBase, SIGINT, signal_cb, m_pBase);
evsignal_add(m_pEvstop, NULL); struct sockaddr_in sin;
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_port = htons(port); m_pListener = evconnlistener_new_bind(m_pBase, accept_conn_cb, NULL,
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE,
-1, (struct sockaddr*)&sin,
sizeof(struct sockaddr_in)); if (NULL == m_pListener)
{
printf("couldn't create listener!\n");
exit(1);
}
} /*
**@author: laoyang360
**@date: 20161211
**@param: 无
**@brief: 启动,循环执行
*/
void YuLibeventServer::start()
{
event_base_dispatch(m_pBase);
} /*
**@author: laoyang360
**@date: 20161211
**@param: 无
**@brief: 停止
*/
void YuLibeventServer::stop()
{
if (NULL != m_pListener)
{
evconnlistener_free(m_pListener);
}
if (NULL != m_pEvstop)
{
event_free(m_pEvstop);
}
if (NULL != m_pBase)
{
event_base_free(m_pBase);
}
}

//客户端核心代码参考地址:

#include "yuLibEventClient.h"

/*
**@author: laoyang360
**@date: 20161211
**@brief: The client of SimLibeventClient
*/ YuLibeventClient *YuLibeventClient::pThis = NULL;
const static char* s_serverIpAddr = "127.0.0.1";
const static int s_iBlockSize = 10;
const static long s_iTimeOut = 10; //超时时间
const static int s_iSessionCnt = 10;
int YuLibeventClient::m_siLtotal_bytes_read = 0;
int YuLibeventClient::m_siLtotal_messages_read = 0; YuLibeventClient::YuLibeventClient()
{
pThis = this; //将this指针赋给pThis,使得回调函数能通过pThis指针访问本对象
m_pBase = NULL;
m_pListener = NULL;
m_pszMsg = NULL;
m_evtimeout = NULL;
m_bevs = NULL;
} YuLibeventClient::~YuLibeventClient()
{ } /*
**@author: laoyang360
**@date: 20161211
**@param: evutil_socket_t fd
**@brief: 设置非阻塞,禁止Nagle算法。
*/
void YuLibeventClient::set_tcp_no_delay(evutil_socket_t fd)
{
int iOne = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&iOne, sizeof iOne);
} /*
**@author: laoyang360
**@date: 20161211
**@param: evutil_socket_t fd, short events, void *arg
**@brief: 超时回调函数。
*/
void YuLibeventClient::timeoutcb(evutil_socket_t fd, short events, void *arg)
{
struct event_base *base = (event_base*)arg;
printf("timeout...\n"); event_base_loopexit(base, NULL);
} /*
**@author: laoyang360
**@date: 20161211
**@param: int fd, short events, void* arg
**@brief: 暂时未使用
*/
void YuLibeventClient::cmd_msg_cb(int fd, short events, void* arg)
{
printf("server_msg_cb ing....\n");
struct bufferevent* bev = (struct bufferevent*)arg; char msg[1024] = "testlaoyang20161210";
int iLen = 1 + strlen(msg);
/*int iLen = bufferevent_read(bev, msg, sizeof(msg));
if (0 == iLen)
{
printf("recv message empty.\n");
exit(1);
}*/ //把终端的消息发送给服务器端
bufferevent_write(bev, msg, iLen);
} /*
**@author: laoyang360
**@date: 20161211
**@param: struct bufferevent* bev, void* arg
**@brief: writecb回调函数,暂时未使用
*/
void YuLibeventClient::writecb(struct bufferevent* bev, void* arg)
{
/*
printf("send_server_cb running....\n"); char szSendMsg[1024] = "[writecb: i'am client]";
int iLen = 1 + strlen(szSendMsg);
printf("iLen = %d\n", iLen);
//把终端的消息发送给服务器端
bufferevent_write(bev, szSendMsg, iLen);
*/
} /*
**@author: laoyang360
**@date: 20161211
**@param: struct bufferevent* bev, void* arg
**@brief: readcb回调函数,接收处理回调接口。
*/
void YuLibeventClient::readcb(struct bufferevent* bev, void* arg)
{
char szRecvMsg[1024] = {0};
int len = bufferevent_read(bev, szRecvMsg, sizeof(szRecvMsg));
szRecvMsg[len] = '\0';
printf("recv from server: cnt = %d, len = %d, msg = %s\n", m_siLtotal_messages_read, len, szRecvMsg); ++m_siLtotal_messages_read;
m_siLtotal_bytes_read += len; //把终端的消息发送给服务器端
bufferevent_write(bev, szRecvMsg, len); //以下是chenshuo的使用方法
/*This callback is invoked when there is data to read on bev @by chenshuo below */
//struct evbuffer *input = bufferevent_get_input(bev);
//struct evbuffer *output = bufferevent_get_output(bev);
//++m_siLtotal_messages_read;
//m_siLtotal_bytes_read += evbuffer_get_length(input);
//evbuffer_add_buffer(output, input); } /*
**@author: laoyang360
**@date: 20161211
**@param: struct bufferevent *bev, short event, void *arg
**@brief: eventcb回调函数,事件或出错处理回调接口。
*/
void YuLibeventClient::eventcb(struct bufferevent *bev, short event, void *arg)
{ if (event & BEV_EVENT_EOF)
{
printf("connection closed\n");
}
else if (event & BEV_EVENT_ERROR)
{
printf("some other error\n");
}
else if( event & BEV_EVENT_CONNECTED)
{
printf("the client has connected to server\n");
evutil_socket_t fd = bufferevent_getfd(bev);
set_tcp_no_delay(fd);
}
} /*
**@author: laoyang360
**@date: 20161211
**@param: int iPort, 传入端口。
**@brief: libevent,socket初始化等
*/
void YuLibeventClient::init(int iPort)
{
WSADATA wsaData;
DWORD Ret;
if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
{
printf("WSAStartup failed with error %d\n", Ret);
exit(1);
} m_timeout.tv_sec = s_iTimeOut; //60s超时
m_timeout.tv_usec = 0; m_pszMsg = (char*)malloc(1 + s_iBlockSize);
memset(m_pszMsg, 0, s_iBlockSize);
for (int i = 0; i < s_iBlockSize; ++i)
{
m_pszMsg[i] = 't'; /*i%128;*/
}
m_pszMsg[s_iBlockSize] = '\0';
//printf("m_pszMsg = %s\n", m_pszMsg); m_pBase = event_base_new();
if (!m_pBase)
{
printf("Couldn't open event base!\n");
exit(1);
} //设定超时
m_evtimeout = evtimer_new(m_pBase, timeoutcb, m_pBase);
evtimer_add(m_evtimeout, &m_timeout); struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr) );
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(iPort);
server_addr.sin_addr.s_addr = inet_addr(s_serverIpAddr); m_bevs = (bufferevent**)malloc(s_iSessionCnt * sizeof(struct bufferevent *));
for (int i=0; i < s_iSessionCnt; ++i)
{
struct bufferevent* bev = bufferevent_socket_new(m_pBase, -1, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, readcb, NULL, eventcb, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE); evbuffer_add(bufferevent_get_output(bev), m_pszMsg, s_iBlockSize); if (bufferevent_socket_connect(bev, (struct sockaddr *)&server_addr,
sizeof(server_addr)) < 0)
{
printf("Error starting connection!\n");
bufferevent_free(bev);
exit(1);
}
m_bevs[i] = bev;
} } /*
**@author: laoyang360
**@date: 20161211
**@param: 无
**@brief: 启动,循环执行
*/
void YuLibeventClient::start()
{
event_base_dispatch(m_pBase);
} /*
**@author: laoyang360
**@date: 20161211
**@param: 无
**@brief: 停止,内存等释放&结果统计
*/
void YuLibeventClient::stop()
{
//evconnlistener_free(m_pListener);
if (NULL != m_pBase)
{
event_base_free(m_pBase);
} for (int i = 0; i < s_iSessionCnt; ++i)
{
if (NULL != m_bevs[i])
{
bufferevent_free(m_bevs[i]);
}
} if (NULL != m_bevs)
{
free(m_bevs);
} if (NULL != m_pszMsg)
{
free(m_pszMsg);
} printf("%d total bytes read\n", m_siLtotal_bytes_read);
printf("%d total messages read\n", m_siLtotal_messages_read);
printf("%.3f average messages size read\n", (double)m_siLtotal_bytes_read/m_siLtotal_messages_read);
printf("%.3f MiB/s throughtput\n", (double)m_siLtotal_bytes_read/(m_timeout.tv_sec * 1024 * 1024)); }

4.测试效果如下

5.源码包下载

http://download.csdn.net/detail/wojiushiwo987/9708418

后记: 
项目中用到了libevent,但我自己一直没有总结这块,从去年开始到现在这个想法持续了一年,总算了了心愿。 
代码对大牛陈硕的C的测试代码进行了C++的封装、测试、验证。向大牛的钻研精神和毅力学习和致敬!

参考: 
http://blog.csdn.net/solstice/article/details/6527585 
https://github.com/chenshuo/recipes/tree/master/pingpong/libevent 
http://blog.csdn.net/funkri/article/details/9352955 
http://blog.csdn.net/laoyang360/article/details/8675922

2016年12月11日 22:16 思于宾馆

作者:铭毅天下 
转载请标明出处,原文地址: 
http://blog.csdn.net/laoyang360/article/details/53576132 
如果感觉本文对您有帮助,请点击‘顶’支持一下,您的支持是我坚持写作最大的动力,谢谢!

Windows下libevent C++封装类实现(为什么要使用封装好的网络库?)的更多相关文章

  1. Windows下libevent C++封装类实现

    题记 windows平台下对于服务器高并发的网络模型选型中,使用libevent是个不错的选择. 本文的背景基于:国内博客对于libevent大多介绍linux实现,大多是c语言的实现,Windows ...

  2. windows下libevent的编译及使用

    之前简单分析了libevent的源码,过了一段时间要用的时候发现完全忘记了..从头记录一下流程 1.编译 可以从github下载libevent的压缩包,解压后 修改以下三个文件,添加宏定义: 在以下 ...

  3. 【VS开发】【数据库开发】windows下libevent x64库静态编译

    按照libevent的文档,使用VC的nmake -f Makefile.nmake即可编译32位release模式.因为项目中要求编译64位的版本,需要在Makefile.nmake中添加一个LIB ...

  4. Apache Thrift 在Windows下的安装与开发

    Windows下安装Thrift框架的教程很多.本文的不同之处在于,不借助Cygwin或者MinGW,只用VS2010,和Thrift官网下载的源文件,安装Thrift并使用. 先从官网 下载这两个文 ...

  5. Linux下Libevent安装和简单实用

    前言 Libevent 是一个用C语言编写的.轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大:源代码相 ...

  6. windows下的socket网络编程

    windows下的socket网络编程 windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了, ...

  7. windows下的socket网络编程(入门级)

    windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了,这次因为需要做一个跨平台的网络程序,就先 ...

  8. Windows下编译安装 FFmpeg

    在Linux/Mac下编译 ffmpeg是非常方便的.但要在 Windows下编译 ffmpeg还真要花点时间.以下就是在 Windowns下编译ffmpeg的步骤: 一.安装Cygwin 在wind ...

  9. windows下CEF3的关闭流程《转》

    原文地址:https://github.com/fanfeilong/cefutil/blob/master/doc/CEF_Close.md ============================ ...

随机推荐

  1. NOIP 模拟 玩积木 - 迭代加深搜索 / bfs+hash+玄学剪枝

    题目大意: 有一堆积木,0号节点每次可以和其上方,下方,左上,右下的其中一个交换,问至少需要多少次达到目标状态,若步数超过20,输出too difficult 目标状态: 0 1 1 2 2 2 3 ...

  2. 【codeforces 777D】Cloud of Hashtags

    [题目链接]:http://codeforces.com/contest/777/problem/D [题意] 让你通过删除字符串的末尾字符的方式; 使得所给的字符串以字典序升序排序; 不能交换字符串 ...

  3. 微信小程序bnner滚动

    首先是轮播图,autoplay 自动播放,interval 轮播的时间,duration 切换速度,可以根据自己需求去添加. Delete:是删除按钮,加载进来是隐藏的,需用户点轮播图进去后,轮播图全 ...

  4. Java获取URL对应的资源

    Java获取URL对应的资源   认识IP.认识URL是进行网络编程的第一步.java.net.URL提供了丰富的URL构建方式,并可以通过java.net.URL来获取资源.   一.认识URL   ...

  5. Facebook学长交流分享

    印象中这似乎是 Facebook 第一次来浙大校园做交流. 前不久,也参加了 Google 的校园宣讲,G 的两大宣讲主题是 Google 介绍和模拟面试.印象最深的是无敌的一家三口每年 100w 封 ...

  6. BS_OWNERDRAW风格的作用和例子(值得研究,待续)

    TBitBtn就是一个例子: procedure TBitBtn.CreateParams(var Params: TCreateParams); begin inherited CreatePara ...

  7. [Example of Sklearn] - SVM usge

    reference : http://www.csdn.net/article/2012-12-28/2813275-Support-Vector-Machine SVM是什么? SVM是一种训练机器 ...

  8. Method and apparatus for establishing IEEE 1588 clock synchronization across a network element comprising first and second cooperating smart interface converters wrapping the network element

    Apparatus for making legacy network elements transparent to IEEE 1588 Precision Time Protocol operat ...

  9. SpringMVC+easyUI 分页,查询 (完整的CRUD)

    最终完毕CRUD的功能了,注意,这里会对前面有一些修改,UserController的listUser() 已经改写了,如今把所有整理一下吧. JSP: <%@ page language=&q ...

  10. HDU 1143 Tri Tiling(递归)

    意甲冠军:一些现有的1*2小盒子.求拼3*n多少个长方形的拼写. 思考: 因为它是一个递归式.肯定会遇到层的关系.仔细观察,研究发现,每层应设置2一层.(奇数层不能是矩形)而从显卡好最后一层的最后一战 ...