使用 epoll 和 boost 库线程池实现并发服务器

1. epoll编程相关

在这里插入图片描述

1.2 epoll编程流程:

epoll编程流程

  1. int epoll_create(int size) 创建用于处理accept的专用文件描述符
 int epfd=epoll_create(10000);
  1. 声明事件数组以及事件
 //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
    struct epoll_event ev,events[10000];
  1. 监听(int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event))
 epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

op: 对监听红黑树所做的操作
取值有:

EPOLL_CTL_ADD 向事件表中注册fd上的事件
EPOLL_CTL_MOD 修改fd上的注册事件
EPOLL_CTL_DEL 删除fd上的注册事件
  1. epoll_wait 监听
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epfd: epoll_create函数的返回值。

events: 他和ctl的epoll_event结构体不一样,它是一个数组(传出参数),传出满足监听事件的文件描述符

maxevents:数组元素的总个数

timeout:-1 阻塞, 0非阻塞,>0超时时间。

返回值:
>0:满足监听的总个数,可以用作循环上限。

2 修改服务器程序

global.h

#ifndef _GLOBAL_H
#define _GLOBAL_H

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>
#include <thread>
#include <vector>
#include <mysql/mysql.h>
#include <unordered_map>
#include <pthread.h>
#include <set>
#include <hiredis/hiredis.h>
#include <fstream>
//以下新增
#include<sys/epoll.h>
#include<boost/bind.hpp>
#include<boost/asio.hpp>
#include<errno.h>
using namespace std;

#endif

server.h 添加设置非阻塞函数

#ifndef SERVER_H
#define SERVER_H

#include "global.h"

class server{
    private:
        int server_port;
        int server_sockfd;
        string server_ip;
        static vector<bool> sock_arr;
        static unordered_map<string,int> name_sock_map;//名字和套接字描述符
        static unordered_map<string,string> from_to_map;//记录用户xx要向用户yy发送信息
        static unordered_map<int,set<int> > group_map;//记录群号和套接字描述符集合
        static pthread_mutex_t name_sock_mutx;//互斥锁,锁住需要修改name_sock_map的临界区
        static pthread_mutex_t group_mutx;//互斥锁,锁住需要修改group_map的临界区
        static pthread_mutex_t from_mutex;//互斥锁,锁住修改from_to_map的临界区
    public:
        server(int port,string ip);
        ~server();
        void run();
        static void RecvMsg(int epollfd,int conn);
        static void HandleRequest(int epollfd,int conn,string str,tuple<bool,string,string,int,int> &info);
        static void setnonblocking(int conn); //将套接字设为非阻塞
};
#endif

setnonblocking(int conn)

//将参数的文件描述符设为非阻塞
void server::setnonblocking(int sock)
{
    int opts;
    opts=fcntl(sock,F_GETFL);
    if(opts<0)
    {
        perror("fcntl(sock,GETFL)");
        exit(1);
    }
    opts = opts|O_NONBLOCK;
    if(fcntl(sock,F_SETFL,opts)<0)
    {
        perror("fcntl(sock,SETFL,opts)");
        exit(1);
    }
}

sever.cpp

#include "server.h"

vector<bool> server::sock_arr(10000,false);
unordered_map<string,int> server::name_sock_map;//名字和套接字描述符
unordered_map<string,string> server::from_to_map;//记录用户xx要向用户yy发送信息
unordered_map<int,set<int> > server::group_map;//记录群号和套接字描述符集合
pthread_mutex_t server::name_sock_mutx;//互斥锁,锁住需要修改name_sock_map的临界区
pthread_mutex_t server::group_mutx;//互斥锁,锁住需要修改group_map的临界区
pthread_mutex_t server::from_mutex;//自旋锁,锁住修改from_to_map的临界区

server::server(int port,string ip):server_port(port),server_ip(ip){
    pthread_mutex_init(&name_sock_mutx, NULL); //创建互斥锁
    pthread_mutex_init(&group_mutx, NULL); //创建互斥锁
    pthread_mutex_init(&from_mutex, NULL); //创建互斥锁
}

将run函数的流程进行修改;
先epoll进行监听,之后调用线程池中的线程进行接收请求;

//将参数的文件描述符设为非阻塞
void server::setnonblocking(int sock)  
{  
    int opts;  
    opts=fcntl(sock,F_GETFL);  
    if(opts<0)  
    {  
        perror("fcntl(sock,GETFL)");  
        exit(1);  
    }  
    opts = opts|O_NONBLOCK;  
    if(fcntl(sock,F_SETFL,opts)<0)  
    {  
        perror("fcntl(sock,SETFL,opts)");  
        exit(1);  
    }      
}  
void server::run(){
    //listen的backlog大小
    int LISTENQ=200;
    int i, maxi, listenfd, connfd, sockfd,epfd,nfds;
    ssize_t n;
    //char line[MAXLINE];
    socklen_t clilen;
    //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
    struct epoll_event ev,events[10000];
    //生成用于处理accept的epoll专用的文件描述符
    epfd=epoll_create(10000);
    struct sockaddr_in clientaddr;
    struct sockaddr_in serveraddr;
    listenfd = socket(PF_INET, SOCK_STREAM, 0);
    //把socket设置为非阻塞方式
    setnonblocking(listenfd);
    //设置与要处理的事件相关的文件描述符
    ev.data.fd=listenfd;
    //设置要处理的事件类型
    ev.events=EPOLLIN|EPOLLET;
    //注册epoll事件
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
    //设置serveraddr
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");//此处设为服务器的ip
    serveraddr.sin_port=htons(8023);
    bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(listenfd, LISTENQ);
    clilen=sizeof(clientaddr);
    maxi = 0;

    /* 定义一个10线程的线程池 */
    boost::asio::thread_pool tp(10);

    while(1){
        cout<<"--------------------------"<<endl;
        cout<<"epoll_wait阻塞中"<<endl;
        //等待epoll事件的发生
        nfds=epoll_wait(epfd,events,10000,-1);//最后一个参数是timeout,0:立即返回,-1:一直阻塞直到有事件,x:等待x毫秒
        cout<<"epoll_wait返回,有事件发生"<<endl;
        //处理所发生的所有事件
        for(i=0;i<nfds;++i)
        {
            //有新客户端连接服务器
            if(events[i].data.fd==listenfd)
            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
                if(connfd<0){
                     perror("connfd<0");
                     exit(1);
                }
                else{
                    cout<<"用户"<<inet_ntoa(clientaddr.sin_addr)<<"正在连接\n";
                }
                //设置用于读操作的文件描述符
                ev.data.fd=connfd;
                //设置用于注册的读操作事件,采用ET边缘触发,为防止多个线程处理同一socket而使用EPOLLONESHOT
                ev.events=EPOLLIN|EPOLLET|EPOLLONESHOT;
                //边缘触发要将套接字设为非阻塞
                setnonblocking(connfd);
                //注册ev
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            //接收到读事件
            else if(events[i].events&EPOLLIN)
            {
                sockfd = events[i].data.fd;
                events[i].data.fd=-1;
                cout<<"接收到读事件"<<endl;

                string recv_str;
                boost::asio::post(boost::bind(RecvMsg,epfd,sockfd)); //加入任务队列,处理事件
            }
        }
    }
    close(listenfd);
}

接收函数

void server::RecvMsg(int epollfd,int conn){
    tuple<bool,string,string,int,int> info;//元组类型,四个成员分别为if_login、login_name、target_name、target_conn
    /*
        bool if_login;//记录当前服务对象是否成功登录
        string login_name;//记录当前服务对象的名字
        string target_name;//记录目标对象的名字
        int target_conn;//目标对象的套接字描述符
        int group_num;//记录所处群号
    */
    get<0>(info)=false;
    get<3>(info)=-1;

    string recv_str;
    while(1){
        char buf[10];
        memset(buf, 0, sizeof(buf));
        int ret  = recv(conn, buf, sizeof(buf), 0);
        if(ret < 0){
            cout<<"recv返回值小于0"<<endl;
            //对于非阻塞IO,下面的事件成立标识数据已经全部读取完毕
            if((errno == EAGAIN) || (errno == EWOULDBLOCK)){
                    printf("数据读取完毕\n");
                cout<<"接收到的完整内容为:"<<recv_str<<endl;
                cout<<"开始处理事件"<<endl;
                break;
            }
            cout<<"errno:"<<errno<<endl;
            return;
        }
        else if(ret == 0){
            cout<<"recv返回值为0"<<endl;
            return;
        }
        else{
            printf("接收到内容如下: %s\n",buf);
            string tmp(buf);
            recv_str+=tmp;
        }
    }
    string str=recv_str;
    HandleRequest(epollfd,conn,str,info);
}

HandleRequest 函数,由于我们采用的是epolloneshot所以之后我们在处理完请求后需要重新注册epoll事件:

void server::HandleRequest(int epollfd,int conn,string str,tuple<bool,string,string,int,int> &info){
    char buffer[1000];
    string name,pass;
    //把参数提出来,方便操作
    bool if_login=get<0>(info);//记录当前服务对象是否成功登录
    string login_name=get<1>(info);//记录当前服务对象的名字
    string target_name=get<2>(info);//记录目标对象的名字
    int target_conn=get<3>(info);//目标对象的套接字描述符
    int group_num=get<4>(info);//记录所处群号

    //连接MYSQL数据库
    MYSQL *con=mysql_init(NULL);
    mysql_real_connect(con,"127.0.0.1","root","","ChatProject",0,NULL,CLIENT_MULTI_STATEMENTS);

    //连接redis数据库
    redisContext *redis_target = redisConnect("127.0.0.1",6379);
    if(redis_target->err){
        redisFree(redis_target);
        cout<<"连接redis失败"<<endl;
    }

    //先接收cookie看看redis是否保存该用户的登录状态
    if(str.find("cookie:")!=str.npos){
        cout<<"cookie方法\n";
        string cookie=str.substr(7);
        // 查询该cookie是否存在:hget cookie name
        string redis_str="hget "+cookie+" name";
        redisReply *r = (redisReply*)redisCommand(redis_target,redis_str.c_str());
        string send_res;
        //存在
        if(r->str){
            cout<<"查询redis结果:"<<r->str<<endl;
            send_res=r->str;
        }
        //不存在
        else
            send_res="NULL";
        send(conn,send_res.c_str(),send_res.length()+1,0);
    }

    //注册
    else if(str.find("name:")!=str.npos){
        cout<<"注册方法\n";
        int p1=str.find("name:"),p2=str.find("pass:");
        name=str.substr(p1+5,p2-5);
        pass=str.substr(p2+5,str.length()-p2-4);
        string search="INSERT INTO USER VALUES (\"";
        search+=name;
        search+="\",\"";
        search+=pass;
        search+="\");";
        cout<<"sql语句:"<<search<<endl<<endl;
        mysql_query(con,search.c_str());
    }

    //登录
    else if(str.find("login")!=str.npos){
        cout<<"登录方法\n";
        int p1=str.find("login"),p2=str.find("pass:");
        name=str.substr(p1+5,p2-5);
        pass=str.substr(p2+5,str.length()-p2-4);
        string search="SELECT * FROM USER WHERE NAME=\"";
        search+=name;
        search+="\";";
        cout<<"sql语句:"<<search<<endl;
        auto search_res=mysql_query(con,search.c_str());
        auto result=mysql_store_result(con);
        int col=mysql_num_fields(result);//获取列数
        int row=mysql_num_rows(result);//获取行数
        //查询到用户名
        if(search_res==0&&row!=0){
            cout<<"查询成功\n";
            auto info=mysql_fetch_row(result);//获取一行的信息
            cout<<"查询到用户名:"<<info[0]<<" 密码:"<<info[1]<<endl;
            //密码正确
            if(info[1]==pass){
                cout<<"登录密码正确\n\n";
                string str1="ok";
                if_login=true;
                login_name=name;
                pthread_mutex_lock(&name_sock_mutx); //上锁
                name_sock_map[login_name]=conn;//记录下名字和文件描述符的对应关系
                pthread_mutex_unlock(&name_sock_mutx); //解锁

                // 随机生成sessionid并发送到客户端
                srand(time(NULL));//初始化随机数种子
                for(int i=0;i<10;i++){
                    int type=rand()%3;//type为0代表数字,为1代表小写字母,为2代表大写字母
                    if(type==0)
                        str1+='0'+rand()%9;
                    else if(type==1)
                        str1+='a'+rand()%26;
                    else if(type==2)
                        str1+='A'+rand()%26;
                }
                //将sessionid存入redis
                string redis_str="hset "+str1.substr(2)+" name "+login_name;
                redisReply *r = (redisReply*)redisCommand(redis_target,redis_str.c_str());
                //设置生存时间,默认300秒
                redis_str="expire "+str1.substr(2)+" 300";
                r=(redisReply*)redisCommand(redis_target,redis_str.c_str());
                cout<<"随机生成的sessionid为:"<<str1.substr(2)<<endl;

                send(conn,str1.c_str(),str1.length()+1,0);
            }
            //密码错误
            else{
                cout<<"登录密码错误\n\n";
                char str1[100]="wrong";
                send(conn,str1,strlen(str1),0);
            }
        }
        //没找到用户名
        else{
            cout<<"查询失败\n\n";
            char str1[100]="wrong";
            send(conn,str1,strlen(str1),0);
        }
    }

    //设定目标的文件描述符
    else if(str.find("target:")!=str.npos){
        cout<<"设定目标方法\n";
        int pos1=str.find("from");
        string target=str.substr(7,pos1-7),from=str.substr(pos1+5);
        target_name=target;
        if(name_sock_map.find(target)==name_sock_map.end()){
            cout<<"源用户为"<<login_name<<",目标用户"<<target_name<<"仍未登录,无法发起私聊\n";
        }
        else{
            pthread_mutex_lock(&from_mutex);
            from_to_map[from]=target;
            cout<<"from:"<<from<<"  target:"<<target<<endl;
            pthread_mutex_unlock(&from_mutex);
            login_name=from;
            cout<<"源用户"<<login_name<<"向目标用户"<<target_name<<"发起的私聊即将建立";
            cout<<",目标用户的套接字描述符为"<<name_sock_map[target]<<endl;
            target_conn=name_sock_map[target];
        }
    }

    //接收到消息,转发
    else if(str.find("content:")!=str.npos){
        target_conn=-1;
        cout<<"转发方法\n";
         //根据两个map找出当前用户和目标用户
        for(auto i:name_sock_map){
            if(i.second==conn){
                login_name=i.first;
                target_name=from_to_map[i.first];
                target_conn=name_sock_map[target_name];
                break;
            }
        }
        if(target_conn==-1){
            cout<<"找不到目标用户"<<target_name<<"的套接字,将尝试重新寻找目标用户的套接字\n";
            if(name_sock_map.find(target_name)!=name_sock_map.end()){
                target_conn=name_sock_map[target_name];
                cout<<"重新查找目标用户套接字成功\n";
            }
            else{
                cout<<"查找仍然失败,转发失败!\n";
            }
        }
        string recv_str(str);
        string send_str=recv_str.substr(8);
        cout<<"用户"<<login_name<<"向"<<target_name<<"发送:"<<send_str<<endl;
        send_str="["+login_name+"]:"+send_str;
        send(target_conn,send_str.c_str(),send_str.length(),0);
    }

     //绑定群聊号
    else if(str.find("group:")!=str.npos){
        cout<<"绑定群聊号方法\n";
        string recv_str(str);
        string num_str=recv_str.substr(6);
        group_num=stoi(num_str);
        //找出当前用户
        for(auto i:name_sock_map)
            if(i.second==conn){
                login_name=i.first;
                break;
            }
        cout<<"用户"<<login_name<<"绑定群聊号为:"<<num_str<<endl;
        pthread_mutex_lock(&group_mutx);//上锁
        group_map[group_num].insert(conn);
        pthread_mutex_unlock(&group_mutx);//解锁
    }

    //广播群聊信息
    else if(str.find("gr_message:")!=str.npos){
        cout<<"广播群聊信息方法\n";
         //找出当前用户
        for(auto i:name_sock_map)
            if(i.second==conn){
                login_name=i.first;
                break;
            }
        //找出群号
        for(auto i:group_map)
            if(i.second.find(conn)!=i.second.end()){
                group_num=i.first;
                break;
            }
        string send_str(str);
        send_str=send_str.substr(11);
        send_str="["+login_name+"]:"+send_str;
        cout<<"群聊信息:"<<send_str<<endl;
        for(auto i:group_map[group_num]){
            if(i!=conn)
                send(i,send_str.c_str(),send_str.length(),0);
        }
    }

    //线程工作完毕后重新注册事件
    epoll_event event;
    event.data.fd=conn;
    event.events=EPOLLIN|EPOLLET|EPOLLONESHOT;
    epoll_ctl(epollfd,EPOLL_CTL_MOD,conn,&event);

    mysql_close(con);
    if(!redis_target->err)
        redisFree(redis_target);

    //更新实参
    get<0>(info)=if_login;//记录当前服务对象是否成功登录
    get<1>(info)=login_name;//记录当前服务对象的名字
    get<2>(info)=target_name;//记录目标对象的名字
    get<3>(info)=target_conn;//目标对象的套接字描述符
    get<4>(info)=group_num;//记录所处群号
}

3 资源

代码

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用epoll线程池实现并发服务器的C++11代码示例: ```cpp #include <iostream> #include <thread> #include <vector> #include <queue> #include <mutex> #include <condition_variable> #include <sys/epoll.h> #include <unistd.h> #define MAX_EVENTS 100 #define THREAD_POOL_SIZE 10 std::mutex mtx; std::condition_variable cv; std::queue<int> taskQueue; void workerThread() { while (true) { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [] { return !taskQueue.empty(); }); int fd = taskQueue.front(); taskQueue.pop(); // 处理任务,这里可以根据具体需求进行处理 lock.unlock(); // 继续监听其他事件 } } int main() { // 创建epoll句柄 int epoll_fd = epoll_create(1); if (epoll_fd == -1) { std::cerr << "Failed to create epoll" << std::endl; return 1; } // 创建线程池 std::vector<std::thread> threadPool; for (int i = 0; i < THREAD_POOL_SIZE; ++i) { threadPool.emplace_back(workerThread); } // 添加监听事件到epoll句柄 struct epoll_event event; event.events = EPOLLIN; event.data.fd = /* 监听的文件描述符 */; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, /* 监听的文件描述符 */, &event) == -1) { std::cerr << "Failed to add event to epoll" << std::endl; return 1; } // 开始监听事件 struct epoll_event events[MAX_EVENTS]; while (true) { int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (num_events == -1) { std::cerr << "Failed to wait for events" << std::endl; return 1; } for (int i = 0; i < num_events; ++i) { if (events[i].events & EPOLLIN) { // 处理读事件,将任务添加到任务队列 std::lock_guard<std::mutex> lock(mtx); taskQueue.push(events[i].data.fd); cv.notify_one(); } } } // 清理资源 close(epoll_fd); for (auto& thread : threadPool) { thread.join(); } return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值