使用epoll实现读写和推送服务器(二)

     这篇主要介绍上篇的epollloop中使用到的EpollServer、EpollStrea

m的实现。

     Server的话一般会有绑定、连接的步骤; Stream的话只是对套接字的一种封装叫法,其实就是半读写封装到一起了。Server每接受一个新的连接都会返回一个套接字,另外服务器、客户端都是一个套接字,所以这里也有套接的封装,也就是一个Stream了,所以对于Server和Stream的接口定义如下

#ifndef ISTREAM_H
#define ISTREAM_H
#include <string>
#include <stdint.h>
#include <memory>
class DataSink;
class IStream
{
public:
    virtual int32_t Receive(char * buffer, int32_t bufferSize, int32_t &readSize) = 0; //用于接收消息
    virtual void EnqueueMessage(std::string &) = 0; //发送消息,将消息加入到队列
    virtual int32_t Send() = 0; //取出一个消息发送

    virtual void SetDataSink(DataSink *dataSink) = 0; //设置应用层的处理对象
    virtual DataSink * GetDataSink() =0;
};
#endif // ISTREAM_H

    对于套接字的封装如下

   

#ifndef SOCKET_H
#define SOCKET_H
#include <stdint.h>
#include <unistd.h>
class Socket
{
public:
    Socket(): _nativeSocket(0){}
    Socket(int32_t nativeSocket) : _nativeSocket(nativeSocket){}

    virtual ~Socket(){close(_nativeSocket);}
    int32_t GetNativeSocket() const{return _nativeSocket;}
    void SetNativeSocket(int32_t nativeSocket){_nativeSocket = nativeSocket;}

private:
    int32_t _nativeSocket;
};
#endif // SOCKET_H

    BaseStream对其中的DataSink相关接口进行了定义

#ifndef BASICSTREAM_H
#define BASICSTREAM_H
#include "istream.h"
#include "socket.h"
class BasicStream : public IStream, public Socket
{
public:
    BasicStream() = default;
    BasicStream(int32_t nativeSocket) : Socket(nativeSocket){}
    BasicStream(const BasicStream &) = delete;

    void SetDataSink(DataSink *dataSink) override{
        _dataSink = dataSink;
    }

    DataSink* GetDataSink() override{
        return _dataSink;
    }

private:
    DataSink *_dataSink;
};
#endif // BASICSTREAM_H

    EPollStream只是实现了以上接口而已,表明是用于epoll的

#ifndef EPOLLSTREAM_H
#define EPOLLSTREAM_H
#include "basicstream.h"
#include <memory>
#include <queue>
#include <mutex>
class EPollStream : public BasicStream
{
public:
    EPollStream(int32_t nativeSocket) : BasicStream(nativeSocket){}
    EPollStream(const EPollStream &) = delete;
    virtual ~EPollStream(){}

    void EnqueueMessage(std::string &) override;
    int32_t Receive(char *buffer, int32_t bufferSize, int32_t &readSize) override;
    int32_t Send() override;

private:
    std::queue<std::string> _msgQueue; //数据的收发可能会在不同的线程,需要加锁。(1、收到客户端请求,处理后的发送属于同一线程;服务器主动推送在不同的线程(在我的工程中是这样,加入队列时在非epoll线程,发送在epoll线程,可能会有在加入队列时同时也在处理客户端请求并处理结束也需要加入结果到队列。))
    std::mutex mtx;
};

typedef std::shared_ptr<EPollStream> EPollStreamPtr;
#endif // EPOLLSTREAM_H

      实现文件如下

#include "epollstream.h"
#include <iostream>
#include "epollloop.h"

void EPollStream::EnqueueMessage(std::string &msg)
{
    std::lock_guard<std::mutex> lock(mtx);
    _msgQueue.emplace(msg); //行加入消息队列,然后触发一个写事件,下一次轮循到的时候发送
    EPollLoop::Get()->ModifyEpollEvents(EPOLLIN | EPOLLOUT | EPOLLET, GetNativeSocket());
}

int32_t EPollStream::Receive(char *buffer, int32_t bufferSize, int32_t &readSize) //接收消息
{
    readSize = 0;
    int32_t nread = 0;

    while ((nread = ::read(GetNativeSocket(),buffer + readSize, bufferSize - 1)) > 0) {
        readSize += nread;
    }

    return nread;
}

int32_t EPollStream::Send() //取出一条消息发送,由epoll循环时自动调用
{
    std::string bytes;
    {
        std::lock_guard<std::mutex> lock(mtx);
        if(_msgQueue.empty()){
            return 0;
        }
        bytes = std::move(_msgQueue.front());
        _msgQueue.pop();
    }

    const char *buf = bytes.data();
    int32_t size = bytes.size();
    int32_t n = size;

    while (n > 0) {
        int32_t nwrite = ::write(GetNativeSocket(),buf + size - n, n);
        if(nwrite < n){
            if(nwrite == -1 && errno != EAGAIN){
                std::cout << "FATAL write data to peer failed!" << std::endl;
            }
            break;
        }
        n -= nwrite;
    }

    return 0;
}

  Server的话也是一个Socket,另外当有新连接到来时需要返回一个新连接,这个类不同的地方可能会不一样,所以用了模板

#ifndef BASICSERVER_H
#define BASICSERVER_H
#include "socket.h"
#include <string>
#include "datasink.h"

template<class ConnectionType>
class BasicServer : public Socket
{
public:
    BasicServer(){}
    virtual int32_t Listen(const std::string &host, int32_t port, int backlog) = 0;
    virtual ConnectionType Accept(int32_t listenfd) = 0;

protected:
    virtual int32_t _Bind(const std::string &host, int port) = 0;
};
#endif // BASICSERVER_H

 EPollServer是用于epoll中的一种Server实现方式,也就是BasicServer的一种实现

#ifndef EPOLLSERVER_H
#define EPOLLSERVER_H

#include "basicserver.h"
#include "epollconnection.h"
class EPollServer : public BasicServer<EPollConnectionPtr>
{
public:
    EPollServer() = default;
    int32_t Listen(const std::string &host, int32_t port, int backlog) override;

    EPollConnectionPtr Accept(int32_t sockfd) override;

protected:
    int32_t _Bind(const std::string &host, int port) override;

private:
    DataSink *_dataSink;
};

#endif // EPOLLSERVER_H

这里的EPollConnectionPtr也就是EPollStream了,只是简单的继承和typedef了一下

#ifndef EPOLLCONNECTION_H
#define EPOLLCONNECTION_H

#include "epollstream.h"
class EPollConnection : public EPollStream
{
public:
    EPollConnection(int32_t nativeSocket) : EPollStream(nativeSocket){}
    EPollConnection(const EPollConnection &) = delete;
    virtual ~EPollConnection(){}
};

typedef std::shared_ptr<EPollConnection> EPollConnectionPtr;
#endif // EPOLLCONNECTION_H
#include "epollserver.h"
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <iostream>
#include "epollloop.h"
#include <cassert>

int32_t EPollServer::_Bind(const std::string &host, int port)
{
    int32_t listenfd;
    if((listenfd = ::socket(AF_INET,SOCK_STREAM, 0)) < 0)
    {
        std::cout << "Create socket failed!" << std::endl;
        ::exit(1);
    }

    SetNativeSocket(listenfd);
    int32_t option =1;
    ::setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&option,sizeof(option));
    ::fcntl(listenfd,F_SETFL,O_NONBLOCK);

    sockaddr_in addr;
    ::bzero(&addr,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = ::htons(port);
    addr.sin_addr.s_addr = ::inet_addr(host.data());

    int32_t errorCode = ::bind(listenfd, (sockaddr*)&addr, sizeof(addr));
    if(errorCode < 0){
        std::cout << "Bind Socket failed!" << std::endl;
    }

    return errorCode;
}

int32_t EPollServer::Listen(const std::string &host, int32_t port, int backlog)
{
   int32_t errorCode =  _Bind(host,port);
   if(errorCode < 0){
       return errorCode;
   }

   errorCode = ::listen(GetNativeSocket(), backlog);
   if(-1 == errorCode){
       std::cout << "Listen socket failed!" << std::endl;
       return errorCode;
   }
   //将Server的套接字加入epoll监听
   errorCode = ::EPollLoop::Get()->AddEpollEvents(EPOLLIN | EPOLLET,GetNativeSocket());
   if(-1 == errorCode){
       assert(0);
       return errorCode;
   }
   //将this对象加入到EPollLoop的Map映射中,用于事件触发时的回调,Rector模式
   EPollLoop::Get()->AddServer(GetNativeSocket(),this);
}

EPollConnectionPtr EPollServer::Accept(int32_t sockfd)
{
    int32_t conn_sock;
    int32_t addrlen;
    sockaddr_in remote;

    int32_t listenfd = GetNativeSocket(); //非阻塞套接字的连接方式
    while ((conn_sock = ::accept(listenfd,(sockaddr*)&remote,(socklen_t*)&addrlen)) > 0) {
        int opts = ::fcntl(conn_sock,F_GETFL);
        ::fcntl(conn_sock,F_SETFL,opts | O_NONBLOCK);
        EPollLoop::Get()->AddEpollEvents(EPOLLIN | EPOLLET, conn_sock);

        EPollConnectionPtr connection = std::make_shared<EPollConnection>(conn_sock);
        return connection; //返回一个连接好的连接对象
    }

    if(-1 == conn_sock){
        if(errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
            std::cout << "error accept" << std::endl;
    }

    return EPollConnectionPtr(nullptr);
}

      由此一个简单的支持推送和问答模式的Server就做好了,应用层面没有写,只是基本的通讯部分有了;对于应用层面的话可以实现DataSink这个接口,然后在EPoolLoop中会进行接口调用;需要注意的时这里调用的这个接口耗时不要太长,因为是在EPollLoop中执行的,处理完的消息可以通过EPollStream的EnqueueMessage接口发送。推送的话也是调用EPoolStream的EnqueueMessage就可以了。工程源码到此下载

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值