上文已经介绍过了,当Windows因为防火墙问题,可能导致evutil_socketpair失败,那么我们可以利用定时器的方式来添加新连接的用户,在一章中,修改如下
#include <thread>
#define CLIENT_THREAD_COUNT 2
struct tagThreadContext
{
std::thread thd;
std::mutex mtx;
std::unordered_set<evutil_socket_t> tmpSocks;
struct event_base* base;
}g_ThreadContext[CLIENT_THREAD_COUNT];
static void client_event_loop(int idx);
static void client_register_cb(evutil_socket_t, short, void *);
static bool init_client_dispatch()
{
for (int i = 0; i < CLIENT_THREAD_COUNT; ++i)
{
std::thread thd(&client_event_loop,i);
thd.detach();
}
return true;
}
static void client_event_loop(int idx)
{
printf("the client_event_loop thread id: %d, idx=%d\n", GetCurrentThreadId(), idx);
struct event_base* base = event_base_new();
g_ThreadContext[idx].base = base;
/*EV_PERSIST表示该事件永久有效,这样当没有客户端连接时,这个event_base_loop也不会结束
*/
struct event* timer = event_new(base, -1, EV_TIMEOUT | EV_PERSIST, client_register_cb,(void*)idx);
struct timeval timeout = { 0, 100 };
evtimer_add(timer, &timeout);
event_base_dispatch(base);
event_free(timer);
event_base_free(base);
}
static void client_register_cb(evutil_socket_t fd, short evt, void * arg)
{
int idx = (int)arg;
while (true)
{
std::unique_lock<std::mutex> guard(g_ThreadContext[idx].mtx);
if (g_ThreadContext[idx].tmpSocks.empty())
{
break;
}
auto itr = g_ThreadContext[idx].tmpSocks.begin();
evutil_socket_t sock = *itr;
g_ThreadContext[idx].tmpSocks.erase(itr);
guard.unlock();
struct event_base *base = g_ThreadContext[idx]->base;
struct bufferevent *bev;
//将fd,也就是socket与iocp绑定在一起,BEV_OPT_THREADSAFE标识bufferevent是线程安全的,内部将会使用了平台自带的锁去加锁。
//这样,在别的线程中 bufferevent_read/bufferevent_write/bufferevent_free(bev)将会是线程安全的,不过需要在一开始的时候
//调用evthread_use_windows_threads()、evthread_use_pthreads()告诉平台启用多线程
bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_THREADSAFE);
if (!bev)
{
fprintf(stderr, "Error constructing bufferevent!");
evutil_closesocket(fd);
//强制结束main里面的event_base_dispatch,这样整个服务端结束
event_base_loopbreak(base);
return;
}
printf("the listener_cb thread id: %d, bev=%p\n", GetCurrentThreadId(), bev);
//设置读写和客户端连接状态的回调函数
bufferevent_setcb(bev, conn_readcb, conn_writecb, conn_eventcb, (void *)9);
//设置读写回调是否可用
bufferevent_enable(bev, EV_WRITE | EV_READ);
//向客户端写数据
bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}
}
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data)
{
/*
服务器开启多线程时,实际上是只负责监听连接事件,当收到连接时,就会把链接的对象抛给event_base_dispatch()
所以我们可以看到这个 listener_cb()实际就是在event_base_dispatch()所在的线程中执行的。
为了把读写操作放到另外的线程中执行,我们必须要重新创建 event_base.
这里面利用一个非常巧妙的设计,就是在新开启的线程中创建一个event_base,并进入自己的事件循环,然后在这个时间循环中,
开启一个很短的定时器,定时器不断的判断是否有新的连接需要注册到event_base中,同时所有的读写操作在这个里面完成。
*/
int idx = fd % CLIENT_THREAD_COUNT;
std::lock_guard<std::mutex> guard(g_ThreadContext[idx].mtx);
g_ThreadContext[idx].tmpSocks.insert(fd);
return;
}