使用empoll实现读写和推送服务器(一)

       linux下的服务大多都是epoll,网上有很多都是Requset-Response类型的服务器; 自己项目中有推送的需要,所以自己也做了个,另外我阅读了epoll写操作事件触发机制感觉写得很全,自己也测试过,写了个server,与大家分享。

     为了好濱示推送,我把它与Qt项目结合了,Qt只是为了产生推送的消息,其它全为纯c++代码。epoll的监听是一个死循环,我定义了如下接口。

#ifndef LOOP_H
#define LOOP_H

class Loop
{
public:
    virtual ~Loop(){}
protected:
    virtual void _Run() = 0;//循环函数
};
#endif // LOOP_H
#ifndef EPOLLLOOP_H
#define EPOLLLOOP_H

#include "loop.h"
#include "epollstream.h"
#include <sys/epoll.h>
#include <vector>
#include <map>

class EPollServer;
class EPollLoop : public Loop
{
public:
    static EPollLoop *Get(); //单例模式
    ~EPollLoop() override;

    void AddServer(int socket,EPollServer *server); //添加一个服务到epoll中
    void AddStream(EPollStreamPtr stream); //添加一个连接好的连接
    void WriteMsg(std::string &bytes, std::vector<int32_t> fds = std::vector<int32_t>());//发送消息,可用于推送,也可用于客户端请求的回应

    int32_t AddEpollEvents(int32_t events, int32_t fd); //添加监听事件
    int32_t ModifyEpollEvents(int32_t events, int32_t fd); // 更改监听事件

protected:
    EPollLoop();
    virtual void _Run() override;

private:

    int32_t _eventfd;
    bool _shutdown;
    std::map<int32_t,EPollServer*> _servers;
    std::map<int32_t,EPollStreamPtr> _streams;

    void _Initialize();
    void _EPollThread(); //监听线程函数
    int32_t _Accept(int32_t eventfd, int32_t listenfd); //处理新连接
};

#endif // EPOLLLOOP_H
#include "epollloop.h"
#include <signal.h>
#include <iostream>
#include <cassert>
#include <thread>
#include "epollserver.h"
#include <string.h>
#include <errno.h>
#include <QDebug>

EPollLoop::EPollLoop()
{
    ::sigset_t set;
    ::sigemptyset(&set);
    ::sigaddset(&set,SIGPIPE);
    ::sigprocmask(SIG_BLOCK,&set,nullptr);
    _Initialize();
}

void EPollLoop::_Run() //启动epll监听线程函数,并与主线程分离
{
    auto func = std::bind(&EPollLoop::_EPollThread,this);
    std::thread listenThread(func);
    listenThread.detach();
}

void EPollLoop::_Initialize() //创建epoll句柄
{
    _eventfd = ::epoll_create1(0);
    if(-1 == _eventfd){
        std::cout << "epoll_create failed" << std::endl;
        assert(0);
    }
    _Run();
}

void EPollLoop::_EPollThread()
{
    std::cout << "_EPollThread" << std::endl;
    epoll_event events[1024];
    while (!_shutdown) {
        int32_t nfds;
        nfds = ::epoll_wait(_eventfd,events,1024,-1); //-1表示没有事件,永久阻塞
        if(-1 == nfds){
            std::cout << "FATAL epoll_wait failed" << std::endl;
            exit(EXIT_FAILURE);
        }

        qDebug() << "event fd count" << nfds;
        for(int i = 0; i < nfds; i++){
            int32_t fd = events[i].data.fd;
            qDebug() << "event fd" << fd;
            if(_servers.find(fd) != _servers.end()){ 
                _Accept(_eventfd,fd); //处理新连接
                continue;
            }

            EPollStreamPtr stream = _streams[fd];
            if(events[i].events & EPOLLIN){ //处理读事件
                std::cout << "Read" << std::endl;
                char buffer[1024];
                int32_t readSize;
                int32_t nread = stream->Receive(buffer,1024,readSize);

                if((-1 == nread && errno != EAGAIN) || 0 == readSize){
                    _streams.erase(stream->GetNativeSocket());
                    std::cout << "errno: " << errno << strerror(errno) << "nread: " << nread << "bytes: " << nread << std::endl;
                }
            }
            
            if(events[i].events & EPOLLOUT){ //处理写事件
                std::cout << "Write" << std::endl;
                stream->Send();
                ModifyEpollEvents(EPOLLIN | EPOLLET,fd); //继续监听写事件
            }
        }
    }
}

int32_t EPollLoop::_Accept(int32_t eventfd, int32_t listenfd) //通过map映射调用server接口,处理新连接,Reactor模式的典型应用
{
    std::cout << "_Accept" << std::endl;
    EPollServer* server = _servers.find(listenfd)->second;
    EPollConnectionPtr connnection = server->Accept(eventfd);

    if(connnection != nullptr){
        _streams[connnection->GetNativeSocket()] = connnection;
    }
}

EPollLoop *EPollLoop::Get()
{
    static EPollLoop epollLoop;
    return &epollLoop;
}

EPollLoop::~EPollLoop()
{
    _shutdown = true;
}

void EPollLoop::AddServer(int socket, EPollServer *server)
{
    _servers.insert({socket,server});
}

void EPollLoop::AddStream(EPollStreamPtr stream)
{
    _streams[stream->GetNativeSocket()] = stream;
}

void EPollLoop::WriteMsg(std::string &bytes, std::vector<int32_t> fds) //通过epoll主动推送消息,用于在客户端没有请求时
{
    if(fds.empty()){
        for(auto iter = _streams.begin(); iter != _streams.end(); iter++){
            iter->second->EnqueueMessage(bytes);
        }
    }else{
        for(auto iter = _streams.begin(); iter != _streams.end(); iter++){
            for(auto fd : fds){
                if(iter->first == fd){
                    iter->second->EnqueueMessage(bytes);
                }
            }
        }
    }
}

int32_t EPollLoop::AddEpollEvents(int32_t events, int32_t fd) //添加监听事件
{
    epoll_event ev;
    ev.events = events;
    ev.data.fd = fd;

    return ::epoll_ctl(_eventfd,EPOLL_CTL_ADD,fd,&ev);
}

int32_t EPollLoop::ModifyEpollEvents(int32_t events, int32_t fd) //更改一个已存在事件的监听事件
{
    epoll_event ev;
    ev.events = events;
    ev.data.fd = fd;

    return ::epoll_ctl(_eventfd,EPOLL_CTL_MOD,fd,&ev);
}

   这上面是epoll处理的核心部分,其它就是Server、EpollStreamPtr、等类和接口的封装了;这里主要用epoll_ctl实现推送,原理就是要写时,先改变监听事件(监听写),这时只要套接字写缓冲区没有被写满epoll_wait都会返回写事件,这样才能保证下次能正常返回写事件,至于原因,可以百度了,关于epoll事件触发,个人经ubuntu16.04测试结果如下。

对于读取操作:

(1) 当buffer由不可读状态变为可读的时候,即由空变为不空的时候。

(2) 当有新数据到达时,即buffer中的待读内容变多的时候。

(3) 当buffer中有数据可读(即buffer不空)且用户对相应fd进行epoll_mod IN事件时。

对于写操作:

(1) 当buffer由不可写变为可写的时候,即由满状态变为不满状态的时候。

(2) 当有旧数据被发送走时,即buffer中待写的内容变少得时候。

(3) 当buffer中有可写空间(即buffer不满)且用户对相应fd进行epoll_mod OUT事件时。

    以下为推送截图

 当初始化时,缓冲区为空可写,会触发一个写事件

   以下为接收截图

   接收的话,我没有做其它操作只是在IDE里面打印出来了。工程会在其它模块讲完之后,上传,有意向的朋友可以先装好linux的Qt我用的是Qt5.9.3,按照要求Qt5以上就可以了

   在这个例子中,客户端连接的读写以及推送都是在epoll的线程中执行,之前用过多线程读写,但有竞争问题;还是选择了这种,毕竟书上也是这样的;另外,我们还需要对收到的客户端消息做业务处理如果是耗时操作,就要考虑线程池了,socket的读写放在一个线程中不容易问题;处理可以放在其它线程中,。

  关于服务器,我也是入门级别,有兴趣的欢迎在评论里点评与指教。 

猜你喜欢

转载自blog.csdn.net/wanghualin033/article/details/81254368
今日推荐