• 项目中的Libevent(多线程)


    多线程版Libevent
    //保存线程的结构体
    struct LibeventThread
    {
        LibEvtServer* that;                            //用作传参
        std::shared_ptr<std::thread>   spThread;    // 线程
        struct event_base * thread_base;            // 事件根基
        struct event   notify_event; 
        evutil_socket_t  notfiy_recv_fd;            // socketpair 接收端fd(工作线程接收通知)
        evutil_socket_t  notfiy_send_fd;            // socketpair 发送端fd(监听线程发送通知)
    #ifdef BOOST_LOCKFREE
        boost::lockfree::spsc_queue<conn_queue_item, boost::lockfree::capacity<1000> > conn_queue; 
    #else
        std::mutex conn_mtx;                        //维护连接队列的锁
        std::queue<conn_queue_item>  conn_queue;    //conn_queue 是一个管理conn_queue_item的队列
    #endif
    };
    
    bool LibEvtServer::init(I_NetServerEvent* event, int start, int size)
    {
        m_ids = new ChannelIDGenerator();
        m_ids->init(start, size);
        m_allChannels.resize(m_ids->getSize());
    
        m_event = event;
        
    
        //event支持windows下线程的函数
        int hr = evthread_use_windows_threads();
        m_base = event_base_new();
        if (!m_base) {
            fprintf(stderr, "Could not initialize libevent!
    ");
            return false;
        }
    #ifdef MUL_LIBEVENT_THREAD
        m_last_thread = -1; //注意初始化为-1
        //初始化线程
        init_threads(THREAD_NUMB);
    #endif
        return true;
    }
    
    bool LibEvtServer::init_threads(int thread_numb)
    {
        m_libevent_threads.resize(thread_numb);
    
        //为每个线程指定双向通道(类似于管道)
        for(int i = 0; i < thread_numb; ++i)
        {
            
            LibeventThread* plt = new LibeventThread();
    #ifdef WIN32
            //创建一个socketpair即可与互相通信的两个socket,保存在fds里面
            evutil_socket_t fds[2];
            if(evutil_socketpair(AF_INET, SOCK_STREAM, 0, fds) < 0) 
            {
                std::cout << "创建socketpair失败
    ";
                return false;
            }
            //设置成无阻赛的socket
            evutil_make_socket_nonblocking(fds[0]);
            evutil_make_socket_nonblocking(fds[1]);
    #else
            int fds[2];
            if (pipe(fds)) {
                perror("Can't create notify pipe");
                exit(1);
            }
    #endif
            plt->notfiy_recv_fd = fds[0];
            plt->notfiy_send_fd = fds[1];
            
            //安装libevent线程[创建base,注册通道事件(用于监听新链接)]
            setup_libevent_thread(plt);
            
            //线程放入容器中
            m_libevent_threads[i] = plt;
        }
    
        //开始创建并启动线程
        for(int i = 0; i < thread_numb; ++i)
        {
            m_libevent_threads[i]->spThread.reset(new std::thread([]
            (void* arg)
            {
                auto me = (LibeventThread*) arg;
                //  Wait for events to become active, and run their callbacks.
                //This is a more flexible version of event_base_dispatch().
                event_base_loop(me->thread_base, 0); 
            }, m_libevent_threads[i]));
        }
        return true;
    }
    
    //设置线程信息
    void LibEvtServer::setup_libevent_thread(LibeventThread * pLibeventThread)
    {
        auto plt = pLibeventThread;
        plt->thread_base = event_base_new(); // 创建线程的event_base
        
        //给每个libevent线程设置连接通知回调函数。
        plt->that = this;
        //设置线程事件notify_event
        event_set(&plt->notify_event, plt->notfiy_recv_fd,//EV_READ表示只要这个socket可读就调用notify_cb函数
            EV_READ | EV_PERSIST, ::notify_cb, plt); 
        //设置事件和event_base的关系
        event_base_set(plt->thread_base, &plt->notify_event); // 设置事件的从属关系(相当于指明事件属于哪个event_base)
        //添加事件
        event_add(&plt->notify_event, 0); // 正式添加事件    
    }
    
    
    void LibEvtServer::listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
        struct sockaddr *sa, int socklen, void *user_data)
    {
    #ifdef MUL_LIBEVENT_THREAD
        int cur_thread = (m_last_thread + 1) %  THREAD_NUMB; // 轮循选择工作线程
        m_last_thread = cur_thread;
    
        conn_queue_item item;
        item.fd = fd;
        //item.ch2 = NULL;
    
        auto  plt = m_libevent_threads[cur_thread];
        {    
            //向线程的队列中放入一个item,每个线程有个队列,保存连接的socketfd
    #ifdef BOOST_LOCKFREE
            while(!plt->conn_queue.push(item))
            {
    #ifndef _DEBUG
                boost::this_thread::interruptible_wait(1);
    #else
                Sleep(1);
    #endif
                Plug::PlugMessageBox("连接队列居然满了,超过1000的未处理数!");
            }
    #else
            std::lock_guard<std::mutex> lock(plt->conn_mtx);
            plt->conn_queue.push(item);
    #endif
        }
        //激活读线程的读事件
        send(plt->notfiy_send_fd, "c", 1, 0);
    
    #else
        auto base = evconnlistener_get_base(listener);
    
        auto bev = bufferevent_socket_new(base, fd, BEV_OPT_THREADSAFE);//|BEV_OPT_CLOSE_ON_FREE);
        if (!bev) 
        {
            fprintf(stderr, "Error constructing bufferevent!");
            event_base_loopbreak(base);
            return ;
        }
    
        auto c2 = CreateChannel(bev);
    
        bufferevent_setcb(bev, conn_readcb, NULL, conn_eventcb, c2);
        bufferevent_enable(bev, EV_READ | EV_WRITE );
        
    #endif
    }
    
    
    
    //侦听端口,-1表示向系统申请一个任意可用端口
    bool LibEvtServer::listen(int* port)
    {
        struct sockaddr_in sin;
    
        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        if(-1 == *port)
            sin.sin_port = htons(10000);
        else
            sin.sin_port = htons(*port);
    
        m_listener = evconnlistener_new_bind(m_base, ::listener_cb, (void*)this,
            LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
            (struct sockaddr*)&sin,
            sizeof(sin));
        if (!m_listener) 
        {
            return false;
        }
    
        if( -1 == *port)
            *port = ntohs(sin.sin_port);
    
        if (!m_listener) {
            fprintf(stderr, "Could not create a listener!
    ");
            return false;
        }
        m_spListenThread.reset(new std::thread([this]//现在看这个线程只是收到连接,然后交给线程,然后通知线程
        {
            //SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
            //event_base_loop(m_base, EVLOOP_ONCE);
            event_base_dispatch(m_base);
            if(WSAENOTSOCK == WSAGetLastError())
            {
                Plug::PlugMessageBox(L"操作无效套接字啊!");
            }
            
            Plug::PlugMessageBox(L"Libevent派发线程退出!");
        }));
        return true;
    }
    
    void LibEvtServer::notify_cb(evutil_socket_t fd, short which, LibeventThread *pLibeventThread)
    {    
        //首先将socketpair的1个字节通知信号读出(这是必须的,在水平触发模式下如果不处理该事件,则会循环通知,直到事件被处理)
        char  buf[1];
        recv(fd, buf, 1, 0);//从sockpair的另一端读数据
    
        auto plt = pLibeventThread;
    
        conn_queue_item  item;
    
        //从自己的连接队列中取出连接数
        {
            //取出队列中的第一个元素
    #ifdef BOOST_LOCKFREE
            while(!plt->conn_queue.pop(item))//pop一个出来
            {
    #ifndef _DEBUG
                boost::this_thread::interruptible_wait(1);
    #else
                Sleep(1);
    #endif
                Plug::PlugMessageBox("通知队列居然弹空了啊!");
            }
    #else
            std::lock_guard<std::mutex>  lck(plt->conn_mtx);
            item = plt->conn_queue.front();
    #endif
        }
    
        //创建每个socket的bufferevent
        auto bev = bufferevent_socket_new(plt->thread_base, item.fd, BEV_OPT_THREADSAFE);
    
        Channel2* c2 = CreateChannel(bev);
    
        //设置接收、状态改变 回调函数
        bufferevent_setcb(bev, conn_readcb, NULL, conn_eventcb, c2);
        bufferevent_enable(bev, EV_READ | EV_WRITE );
    }
    
    //看了这个过程就是这个样子的,监听线程接收到连接之后把这个socket丢给Libevent线程,libevent创建bufferevent
    //处理相关读和写事件,这个工程通过每个线程的连接队列,然后一个socketpair通知的。这样每个线程就很平均的处理所有的连接事件
    //多线程比单线程的是复杂很多,只是这种模式不知道,但bufferevent还是一样的
  • 相关阅读:
    redux-devtools的使用
    Electron-builder打包详解
    HTML JAVASCRIPT CSS 大小写敏感问题
    electron/nodejs实现调用golang函数
    基于react开发package.json的配置
    Chrome插件(扩展)开发全攻略
    Chrome插件中 popup,background,contentscript消息传递机制
    Linux 开机引导和启动过程详解
    bash 的配置文件加载顺序
    常用Electron App打包工具
  • 原文地址:https://www.cnblogs.com/zzyoucan/p/3970578.html
Copyright © 2020-2023  润新知