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

你离开我真会死。 提交于 2019-11-27 23:24:57
    1. 本服务器端框架采用epoll+线程池+任务队列

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

        http://blog.csdn.net/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.csdn.net/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



 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!