libevent服务端,多线程应用2

上文已经介绍过了,当Windows因为防火墙问题,可能导致evutil_socketpair失败,那么我们可以利用定时器的方式来添加新连接的用户,在一章中,修改如下

libevent服务端,多线程应用

#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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值