一个超级简单的服务器框架

1. 本服务器端框架采用epoll+线程池+任务队列

2. Epoll和sernasock是我封装的,ThreadPool用的是

http://blog.youkuaiyun.com/shreck66/article/details/50412986

Epoll.h

/**
 * by:gzh
 * 2017.12.12
 * 可以改进的地方:EPOLLIN 接收数据read()处可以优化,
 *     现在每次可以接收1kb的数据
 */
#ifndef _GZH_EPOLL_H_
#define _GZH_EPOLL_H_
 
#include <vector>
#include <functional>
#include <condition_variable>
#include <list>
#include "net.h"
 
namespace gzhlib
{
 
class GzhEpoll
{
public:
    typedef int Socket;
    typedef struct epoll_event EpollEvent;
    typedef struct sockaddr_in SocketAddr;
    typedef socklen_t SockLen;
    typedef std::tuple<Socket, const char*> ClientMessageTuple;
    typedef std::function<void(const ClientMessageTuple &)> Callback;
    
    //核心代码,服务器接收到数据,在此回调函数中处理
    Callback epollInEvent;
 
private:
    const static int RECEIVE_DATA_MAX = 1024;
 
    //默认最大epoll监听事件数
    const static int EVENTS_MAX = 50000;
    const static int EPOLL_TIME_OUT = -1;
 
    //epoll fd
    int epoll_fd;
    
    //线程池中线程的数目
    int numbersOfThread;
    
    //server fd
    Socket serverSocket; 
 
    //client fd
    Socket clientSocket;
 
    //save client fd;
    //该数组暂时用不到
    std::vector<Socket> vclients;
 
    //自定义的处理的最大事件数
    int maxNumEvents;
 
    //be removing event
    EpollEvent eventWillRemoving;
 
    EpollEvent event;
    
    //EpollEvent *events; 
    EpollEvent events[EVENTS_MAX];
 
    //用来 save client addr
    SocketAddr clientSocketAddr;   
    
    SockLen clientSocketLen;
 
public:
    //通过该函数创建epoll对象
    static GzhEpoll* create(Socket serverSocket, int maxEvents, int threadCounts);
 
    //禁用拷贝构造函数和赋值操作符
    GzhEpoll(const GzhEpoll &) = delete;
    GzhEpoll& operator=(const GzhEpoll &) = delete;
 
    //epoll主循环
    void run();
    
    ~GzhEpoll();
 
private:
    int init(Socket serverSocket, int maxEvents, int threadCounts);
 
    GzhEpoll();
    
    //设置非阻塞模式
    int setNoBlock(Socket sock);  
 
    //设置阻塞模式
    int setBlock(Socket sock);
};
 
}
 
#endif //_GZH_EPOLL_H_
Epoll.cpp

#include <iostream>
#include <algorithm>
#include <tuple>
#include <cstring>
#include "Epoll.h"
#include "ThreadPool.h"
 
using namespace gzhlib;
 
GzhEpoll* GzhEpoll::create(Socket serverSocket, int maxEvents, int threadCounts)
{
    auto pRet = new GzhEpoll();
    pRet->init(serverSocket, maxEvents, threadCounts);
    return pRet;
}
 
int GzhEpoll::init(Socket serverSocket, int maxEvents, int threadCounts)
{
    this->serverSocket = serverSocket;
    this->maxNumEvents = maxEvents;
    this->numbersOfThread = threadCounts;
 
    //设置非阻塞
    setNoBlock(serverSocket);
    
    return true;
}
 
//主循环,对epoll的更多了解,请百度  
void GzhEpoll::run()
{
    //开启线程池
    netlib::ThreadPool threadPool(numbersOfThread);
    threadPool.start();
 
    //等待处理事件的数量
    int numWaitingEvent = 0;
 
    event.events = EPOLLET | EPOLLIN;   //边缘方式触发
    event.data.fd = serverSocket;
 
    epoll_fd = epoll_create(EPOLL_CLOEXEC);   //create epoll,返回值为epoll的文件描述符
    if(epoll_fd < 0)
        hand_error("epoll_create");
    int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, serverSocket, &event);   //添加时间
    if(ret < 0)
        hand_error("epoll_ctl");
 
    while(true)
    {
        numWaitingEvent = epoll_wait(epoll_fd, events, maxNumEvents-1, EPOLL_TIME_OUT); 
        if(numWaitingEvent == -1)
        {
            hand_error("epoll_wait");
        }
        if(numWaitingEvent == 0)
        {
            std::cout<<"numWaitingEvent = "<< numWaitingEvent <<std::endl;
            continue;
        }
        for( int num = 0; num < numWaitingEvent; num ++)
        {
            if(events[num].data.fd == serverSocket) //client connect
            {
                clientSocket = accept(serverSocket, (struct sockaddr*)&clientSocketAddr, &clientSocketLen);
                if(clientSocket < 0)
                {
                    hand_error("accept");
                }
                std::cout<<"one client has connected!"<<std::endl;
                vclients.push_back(clientSocket);
                setNoBlock(clientSocket);   //设置为非阻塞模式
                event.data.fd = clientSocket;// 将新连接也加入EPOLL的监听队列
                event.events = EPOLLIN | EPOLLET ;
                if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, clientSocket, &event)< 0)
                    hand_error("epoll_ctl");
            }
            else if( events[num].events & EPOLLIN)
            {
                clientSocket = events[num].data.fd;
                if(clientSocket < 0)
                    hand_error("cli_sock");
 
                char recvBuffer[RECEIVE_DATA_MAX];
                memset(recvBuffer, 0, sizeof(recvBuffer));
                int num = read(clientSocket, recvBuffer, sizeof(recvBuffer));
                
                if(num == -1)
                {
                    hand_error("read have some problem:");
                }
                else if(num == 0)  //stand of client have exit
                {
                    std::cout<<"one client has exit!"<<std::endl;
                    close(clientSocket);
                    eventWillRemoving = events[num];
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, clientSocket, &eventWillRemoving);
                    vclients.erase(remove(vclients.begin(), vclients.end(), 
                        clientSocket),vclients.end());
                }
                else
                {   
                    //核心代码,将客户端socket和接收到的数据打包成一个任务,放到任务队列里
                    auto tuple = std::make_tuple(clientSocket, recvBuffer);
                    threadPool.append(std::bind(epollInEvent, tuple));
                }
            }
        }
    }
}
 
//设置非阻塞模式
int GzhEpoll::setNoBlock(Socket sock)
{
    int ret = fcntl(sock,  F_SETFL, O_NONBLOCK );
    if(ret < 0)
        hand_error("setnoblock");
    return 0;
}
 
//设置阻塞模式
int GzhEpoll::setBlock(Socket sock)
{
    int ret =  fcntl(sock, F_SETFL, 0);
    if (ret < 0 )
        hand_error("setblock");
    return 0;
}
 
GzhEpoll::GzhEpoll()
{
}
 
GzhEpoll::~GzhEpoll()
{     
    close(epoll_fd);
}
server.h

#ifndef _SERNASOCK_H_
#define _SERNASOCK_H_
 
#include <string.h>
#include <string>
#include <arpa/inet.h>
#include <netinet/in.h>    // for sockaddr_in
#include <sys/types.h>    // for socket
#include <sys/socket.h> 
#include <unistd.h>
 
namespace gzhlib
{
 
class CServerNativeSock
{
private:
    int port;
    int sock;
 
private:
    void init();
 
public:
    CServerNativeSock(uint16_t port);
 
    ~CServerNativeSock(){close(sock);}
 
public:
    
    //获取端口
    uint16_t getPort() {return this->port;};
    
    //得到描述符
    int getSock() {return sock;};
    
    //监听
    int listen(int num) const;
    
    //接收客户端连接
    int accept(struct sockaddr *addr, socklen_t *len) const;
    
    //发送数据
    int send(int cli_sock, const char *szBuffer, size_t len) const;
    
    //接收数据
    int recv(int cli_sock, char *szBuffer, size_t len) const;
    
};
}
#endif //_SERNASOCK_H_
server.cpp

/**
 * CServerNativeSock实现
 */
#include <iostream>
#include "sernasock.h"
 
using namespace gzhlib;
 
//构造函数
CServerNativeSock::CServerNativeSock(uint16_t port):port(port)
{
    init();
}
 
//端口复用,绑定地址
void CServerNativeSock::init()
{
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
        std::cout<< "server socket create error!!!" <<std::endl;
    
    //端口复用
    int opt = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
        (const void *)&opt, sizeof(opt));
 
    //服务器地址
    struct sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(port);
 
    if(bind(sock, (struct sockaddr*)&serAddr, sizeof(serAddr)) < 0)
        printf("bind error\n");
}
    
//监听
int CServerNativeSock::listen(int num) const
{
    if(::listen(sock, num) < 0)
    {
        printf("listen error\n");
        return -1;
    }
    return 0;
}
    
//接收客户端连接
int CServerNativeSock::accept(struct sockaddr *addr, socklen_t *len) const
{
    int cli_sock = ::accept(sock, addr, len);                
    if(cli_sock < 0)
    {
        printf("accept error!!!\n");
        return -1;
    }
    return cli_sock;
}
    
//发送数据
int CServerNativeSock::send(int cli_sock, const char *szBuffer, size_t len) const
{
    return ::send(cli_sock, szBuffer, len, 0);
}
    
//接收数据
int CServerNativeSock::recv(int cli_sock, char *szBuffer, size_t len) const
{
    return ::read(cli_sock, szBuffer, len);
}
测试主函数

test.cpp

#include <iostream>
#include <cstring>
#include <tuple>
#include "Epoll.h"
#include "server.h"
using gzhlib::GzhEpoll;
 
int main()
{
    gzhlib::CServerNativeSock server(8889);
    server.listen(5);
    
    //epoll最大监听1000个事件,开启4个线程
    auto epoll = gzhlib::GzhEpoll::create(server.getSock(), 1000, 4);
    
    //此处处理接收到的数据
    epoll->epollInEvent = [](const GzhEpoll::ClientMessageTuple &clientMessage){
        GzhEpoll::Socket sock = std::get<0>(clientMessage);
        const char *c = std::get<1>(clientMessage);
        write(sock, c, strlen(c));
    };
 
    //开启主循环
    epoll->run();
    return 0;
}
接下来是线程池,详情请移步       http://blog.youkuaiyun.com/shreck66/article/details/50412986

这里只贴代码,不做解释。

ThreadPool.h

#ifndef THREAD_POOL_H_
#define THREAD_POOL_H_
 
#include <thread>
#include <mutex>
#include <condition_variable>
#include <list>
#include <vector>
#include <memory>
#include <functional>
 
namespace netlib
{
    class ThreadPool
    {
        public:
            typedef std::function<void(void)> Task;
            ThreadPool(int threadNumber);
            ~ThreadPool();
 
            //往任务队列里添加任务
            bool append(Task task);
 
            //启动线程池
            bool start(void);
 
            //停止线程池
            bool stop(void);
 
        private:
            //线程所执行的工作函数
            void threadWork(void);    
 
            //互斥锁
            std::mutex mutex_;                                              
            //当任务队列为空时的条件变量
            std::condition_variable_any condition_empty_;                   
            //任务队列
            std::list<Task> tasks_;                                         
            //线程池是否在运行
            bool running_;                                                  
            //线程数
            int threadNumber_;                                              
            //用来保存线程对象指针
            std::vector<std::shared_ptr<std::thread>> threads_;             
    };
}
 
#endif
ThreadPool.cpp

#include "ThreadPool.h"
#include <stdio.h>
#include <thread>
#include <mutex>
#include <memory>
#include <functional>
#include <unistd.h>
#include <iostream>
 
using namespace netlib;
 
ThreadPool::ThreadPool(int threadNumber)
    :threadNumber_(threadNumber),
    running_(true)
{
}
 
ThreadPool::~ThreadPool()
{
    if(running_)
    {
        stop();
    }
}
 
bool ThreadPool::start(void)
{
    for(int i = 0; i < threadNumber_; i++)
    {
        auto t = std::make_shared<std::thread>(std::bind(&ThreadPool::threadWork,this));
        threads_.push_back(t);//循环创建线程      
    }
 
    usleep(500);
    //printf("线程池开始运行\n");
    return true;   
}
 
bool ThreadPool::stop(void)
{
    if(running_)
    {
        running_= false;
        for(auto t : threads_)
        {
            t->join();  //循环等待线程终止
        }
    }
    return true;
}
 
bool ThreadPool::append(Task task)
{
 
    std::lock_guard<std::mutex> guard(mutex_);
    tasks_.push_front(task);   //将该任务加入任务队列
    condition_empty_.notify_one();//唤醒某个线程来执行此任务
    return true;
}
 
void ThreadPool::threadWork(void)
{
    Task task = NULL;
    while(running_)
    {   
        {
            std::lock_guard<std::mutex> guard(mutex_);
            if(tasks_.empty())
            {
                condition_empty_.wait(mutex_);  //等待有任务到来被唤醒
            }
            if(!tasks_.empty())
            {
                task = tasks_.front();  //从任务队列中获取最开始任务
                tasks_.pop_front();     //将取走的任务弹出任务队列
            }
            else
            {
                continue;
            }
        }
        task(); //执行任务
    }
}
net.h

/***********
net.h
***********/
 
#ifndef _NET_H
#define _NET_H
 
#include <sys/types.h>
#include <sys/epoll.h>  //epoll ways file
#include <sys/socket.h>
#include <fcntl.h>    //block and noblock
 
#include <error.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
 
#include <signal.h>
 
#define hand_error(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)
#endif
CMakeLists.txt

#1.cmake verson,指定cmake版本 
cmake_minimum_required(VERSION 3.2)
 
#2.project name,指定项目的名称,一般和项目的文件夹名称对应
#可执行文件的名称
PROJECT(server)
 
#3.头文件在当前目录,可以不用写
INCLUDE_DIRECTORIES(
 
)
 
#4.将需要的源文件放到一个变量里如:SOURCE_FILE
SET(SOURCE_FILE
    Epoll.cpp
    server.cpp
    ThreadPool.cpp
    test.cpp
)
 
#6.add executable file,添加要编译的可执行文件
ADD_EXECUTABLE(${PROJECT_NAME} ${SOURCE_FILE})
 
#7.add link library,添加可执行文件所需要的库,比如我们用到了libm.so(命名规则:lib+name+.so),就添加该库的名称
SET(SHARED_LIB
    pthread
)
TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${SHARED_LIB})
编译步骤:

mkdir build

cd build

cmake ..

make

运行:

./server

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值