最近要做一个关于libevent性能测试的任务,在琢磨如何在TCP服务器上主动发送消息给客户端的时候弄了很久。写篇博客记录一下。
网上的例子大多都是,先建立一个监听回调,监听到之后新建一个bufferevent,然后在bufferevent上建立读写回调,在读回调中对消息进行显示(或者其它处理),然后调用bufferevent_write函数,发送回复信息。
这样的做法,一个非常致命的缺点是,服务器只能进行响应式发送消息,客户端没发送消息的时候,服务器是无法向客户端发送响应信息的。但是有些时候服务器需要主动向客户端发消息,怎么办?
我在网上查了挺久发现没什么有用的信息,在https://bbs.csdn.net/topics/392035999?page=1这里看到有人问了和我一样的问题,有人回答说用bufferevent_write函数。
我想,既然主线程在event_base_dispatch函数阻塞了,那我能不能开一个新线程,在新线程里面调用bufferevent_write,达到主动发送的效果呢!
于是立马动手试了一下,发现好像没用。。只有在接收到信息之后,我想主动发送的消息才会发出去。
我想起libevent要支持多线程好像必须要加个啥函数来着,然后加上去了,成功了!nice啊。不知道libevent的http能不能用同样的方法,让服务器主动发消息。
附上服务器代码(windows版本,linux下只需要小改就行了)
ps:代码是在另一位博主写的服务器代码上改的,附上原文链接:https://blog.csdn.net/woniu211111/article/details/84315998
#include
#include
#include
#include
#include
#include
#ifdef _WIN32
#include
#include
#include
#else
#include
#include
#endif
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "wsock32.lib")
#pragma comment(lib, "libevent.lib")
#pragma comment(lib, "libevent_core.lib")
#pragma comment(lib, "libevent_extras.lib")
using namespace std;
DWORD WINAPI SendThread(void *arg){
struct bufferevent *bev = (struct bufferevent *)arg;
char *p = "我是服务器,我在主动向你发送数据!" ;
while(1){
//写数据给客户端
Sleep(1000);
if(bev) bufferevent_write(bev, p, strlen(p)+1);
}
}
void read_cb(struct bufferevent *bev, void *arg)
{
char buf[1024] = {0};
bufferevent_read(bev, buf, sizeof(buf));
cout << "client say: " << buf << endl;
}
/************************************
@ Brief: 写缓冲区回调
************************************/
void write_cb(struct bufferevent *bev, void *arg)
{
cout << "I'm 服务器,成功写数据给客户端,写缓冲回调函数被调用..." << endl;
}
/************************************
@ Brief: 事件回调
************************************/
void event_cb(struct bufferevent *bev, short events, void *arg)
{
char* ip = (char*)arg;
if (events & BEV_EVENT_EOF)
{
cout << "connection closed:" << ip << endl;
}
else if (events & BEV_EVENT_ERROR)
{
cout << "some other error !" << endl;
}
bufferevent_free(bev);
bev = NULL;
cout << "bufferevent 资源已经被释放..." << endl;
}
/************************************
@ Brief: 监听回调
************************************/
void cb_listener(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int len, void *ptr)
{
struct sockaddr_in* client = (sockaddr_in*)addr ;
cout << "connect new client: " << inet_ntoa(client->sin_addr) << "::"<< ntohs(client->sin_port)<< endl;
struct event_base *base = (struct event_base*)ptr;
//添加新事件
struct bufferevent *bev;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
//给bufferevent缓冲区设置回调
bufferevent_setcb(bev, read_cb, write_cb, event_cb, inet_ntoa(client->sin_addr));
//启动 bufferevent的 读缓冲区。默认是disable 的
bufferevent_enable(bev, EV_READ);
LPDWORD id;
CreateThread(NULL, 0, SendThread, bev, 0, NULL);
}
int main()
{
#ifdef WIN32
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
(void)WSAStartup(wVersionRequested, &wsaData);
evthread_use_windows_threads();
#endif
//init server
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
//创建 event_base
struct event_base * base;
base = event_base_new();
//创建套接字
//绑定
//接收连接请求
struct evconnlistener* listener;
listener = evconnlistener_new_bind(base, cb_listener, base, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, 36, (struct sockaddr*)&serv, sizeof(serv));
//启动循环监听
event_base_dispatch(base);
evconnlistener_free(listener);
event_base_free(base);
return 0;
}
新开的线程做的事情很简单,就是每秒钟主动往客户端发送一句话。