TCP/IP网络编程(8)——聊天室(epoll + 线程池)

目录

前言

代码实现

运行截图


前言

在前文多线程的实现方法中,每连接一个客户端则开辟一个线程与之交互,这种情况下,当客户端连接许多时,就需要开辟非常多的线程,这是不现实的。

本文我采用epoll和多线程综合的方式来实现服务端,同时添加了一些功能,并用面向对象的思想优化了代码结构。使用epoll监听文件描述符发生事件,只有当某个事件发生时,才由线程池执行代码。这样在开辟的线程个数很少的情况下依然能有很高的效率。

实现功能:用户上线下线、查询在线用户、修改用户名、公聊(所有用户可见)、私聊(单一用户可见)。

实现方法:使用epoll监听文件描述符的输入事件。如果是连接请求,则注册新的文件描述符;如果是用户输入,则由线程池执行相应的业务代码,把业务代码放进任务队列,线程不断从任务队列取任务并执行,将处理结果发送给相应客户端。

代码实现

1. user.h

封装了服务端的用户对象的内容

#include<string>
#include<unistd.h>

class User {
    public:
    User(int sock, std::string name) {
        _sock = sock;
        _name = name;
    }

    ~User() {
        close(_sock);
    }

    friend class Server;    //方便server访问

    private:
    int _sock;           //服务端客户套接字
    std::string _name;   //用户名
};

//实例化user对象
User* NewUser(int sock, std::string name) {
    return new User(sock, name);
}

2. threadpool.h

线程池对象类,包括初始化线程数组,添加任务等功能

/*

该程序实现了一个线程池

类的数据成员:存储线程对象的数组、存储任务的队列、互斥锁、条件变量、一个bool量表示是否结束工作

*/

#include<iostream>
#include<vector>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<queue>
#include<functional>


class ThreadPool

{
    public:
    ThreadPool(int thread_num):stop(false)
    {
        std::function<void()> func = [this]()->void{
            while(1) //用while循环执行
            {
                std::unique_lock<std::mutex> lg(mt);
                cv.wait(lg, [this]()->bool{return tasks.size() != 0 || stop == true;}); //通知结束则停止等待
                if(stop && !tasks.size())  //如果结束工作并且队列为空则返回
                    return;
                std::function<void()> task = std::move(tasks.front()); //使用move传入右值引用提高效率
                tasks.pop();
                lg.unlock();
                task();
            }
        };
        for(int i=0;i<thread_num;i++)
            threads.emplace_back(func);  
    }

    ~ThreadPool()
    {
        {
            std::unique_lock<std::mutex> lg(mt);
            stop = true;
        }  //作用域!为了解锁
        cv.notify_all(); //通知所有阻塞线程
        for(std::thread& it : threads)
            it.join(); //等待所有线程执行完毕
    }

    template<class F, class... Args>
    void AddTask(F&& f, Args&&... args) //&&配合forward完美转发实现万能引用,传入左值则为左值,传入右值则为右值

    {
        std::function<void()> task = std::bind(
            std::forward<F>(f), std::forward<Args>(args)...); //bind绑定函数和参数,函数统一为func()
        {
            std::unique_lock<std::mutex> lg(mt);
            tasks.emplace(task);
        } //作用域

        cv.notify_one(); //通知一个阻塞的线程
    }

    private:
    std::vector<std::thread> threads; 
    std::queue<std::function<void()>> tasks;
    std::mutex mt;
    std::condition_variable cv;
    bool stop;
};


ThreadPool* NewThreadPool(int thread_num) {
    return new ThreadPool(thread_num);
}

3. epoll.h

封装了epoll_create()、epoll_ctl()、epoll_wait()函数

#include<sys/wait.h>
#include<sys/epoll.h>
#include<unistd.h>

const int MAX_EVENTS_NUM = 50;  //events数组的大小

class Epoll {
    public:
    Epoll() { 
        _epollfd = epoll_create(MAX_EVENTS_NUM);   //创建epoll例程空间
        _events = new epoll_event[MAX_EVENTS_NUM];   //设置最大event的个数

    }

    ~Epoll() {
        close(_epollfd);
        delete[] _events;
    }

    //注册文件描述符
    void FdAdd(int sock) {
        _event.events = EPOLLIN;
        _event.data.fd = sock;
        epoll_ctl(_epollfd, EPOLL_CTL_ADD, sock, &_event);  //监控输入方式注册server_socket
    }

    //注销文件描述符
    void FdDel(int sock) {
        epoll_ctl(_epollfd, EPOLL_CTL_DEL, sock, NULL);
    }

    int Wait() { 
        return epoll_wait(_epollfd, _events, MAX_EVENTS_NUM, -1);
    }

    friend class Server;    //方便server访问

    private:
    int _epollfd;           //epoll的例程文件描述符
    struct epoll_event _event;   //创建epoll事件结构体
    struct epoll_event* _events;   //保存epoll事件
};

Epoll* NewEpoll() {
    return new Epoll;
}

4. server.h

服务端的类,封装了threadpool、epoll、user对象,包含服务端开启运行、用户上线下线、处理消息、转发消息等功能

#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<chrono>
#include<iomanip>
#include<sstream>
#include<map>

#include"user.h"
#include"epoll.h"
#include"threadpool.h"

const int LISTEN_SIZE = 5;      //请求队列大小
const int BUFF_SIZE = 1024;     //接受消息缓冲大小
const int TP_SIZE = 5;          //线程池线程个数

class Server {
    public:
    Server(char* ip, char* port) {
        _ip = ip;
        _port = port;
        _addr_len = sizeof(_addr);

        int opt = 1;
        _sock = socket(PF_INET,SOCK_STREAM,0);
        setsockopt(_sock,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt)); //创建套接字并避免timeout

        _tp = NewThreadPool(TP_SIZE);     //实例化threadpool

        _epoll = NewEpoll();       //实例化epoll

        _epoll->FdAdd(_sock);      //注册文件描述符
    }

    ~Server() {
        delete _epoll;     //释放epoll

        delete _tp;     //释放threadpool

        close(_sock);
    }

    //监听连接
    void Start(); 

    //发送消息
    void SendMsg(std::string, User*, User*);

    //用户上线
    void OnLine(int);

    //用户下线
    void OffLine(User* user);

    private:
    int _sock;                  //套接字
    struct sockaddr_in _addr;   //地址
    socklen_t _addr_len;
    char* _ip;
    char* _port;
    Epoll* _epoll;                //epoll对象
    std::map<int, User*> _SockMap;   //记录文件描述符的map
    std::map<std::string, User*> _UserMap;   //记录用户的map
    std::mutex _sock_mt;          //sockmap的锁
    std::mutex _user_mt;          //usermap的锁
    ThreadPool* _tp;              //threadpool对象
};

//server运行
void Server::Start() {
    memset(&_addr,0,_addr_len); //设置地址
    _addr.sin_family = AF_INET;
    _addr.sin_addr.s_addr = inet_addr(_ip);
    _addr.sin_port = htons(atoi(_port));

    if(bind(_sock,(struct sockaddr*)&_addr,_addr_len)==-1) //分配套接字地址
        std::cout << "bind error" << std::endl;

    if(listen(_sock,LISTEN_SIZE)==-1) //开启监听
        std::cout << "listen error" << std::endl;

    std::cout << "listening..." << std::endl;

    while(1)
    {
        int eventcount;  //记录epoll_wait返回值
        eventcount = _epoll->Wait();
        if(eventcount == -1) 
            std::cout << "epoll_wait error" << std::endl;
        else //有文件描述符有输入
        {
            for(int i=0;i<eventcount;i++)
            {
                int from_sock = _epoll->_events[i].data.fd;
                if(from_sock == _sock)  //说明有连接请求
                {
                    int conn = accept(_sock, nullptr, nullptr);
                    if(conn == -1) 
                        std::cout << "accept error" << std::endl;

                    //用户上线
                    this->OnLine(conn);
                }
                else //客户端有输入
                {
                    char buf[BUFF_SIZE];
                    int len = recv(from_sock, buf, BUFF_SIZE, 0);
                    
                    if(len == 0) {          //数据大小为0说明断开连接
                        //用户下线
                        this->OffLine(_SockMap[from_sock]);
                    }
                    else {                  
                        std::string msg = buf;
                        msg = msg.substr(0, len-1);     //去掉换行符

                        //将用户信息处理过程添加到线程池的任务队列
                        _tp->AddTask([this, from_sock, msg](){
                            if(msg == "who") {          //查询在线用户
                                std::string sendmsg = "[";
                                _sock_mt.lock();
                                for(auto& it : _SockMap) {
                                    sendmsg += " " + it.second->_name + " |";
                                }
                                _sock_mt.unlock();
                                sendmsg.pop_back();
                                sendmsg += "]";
                                SendMsg(sendmsg, nullptr, _SockMap[from_sock]);
                            }
                            else if(msg.length() > 7 && msg.substr(0, 7) == "rename|") {     //改名
                                std::string newname = msg.substr(7, msg.length()-7);

                                //用户名已存在
                                if(_UserMap.count(newname)) {
                                    SendMsg("User Name Exists", nullptr, _SockMap[from_sock]);
                                    return;
                                }

                                User* user = _SockMap[from_sock];
                                _user_mt.lock();
                                _UserMap.erase(user->_name);
                                user->_name = newname;
                                _UserMap[newname] = user;
                                _user_mt.unlock();
                            } 
                            else if(msg.length() > 3 && msg.substr(0,3) == "to|") {     //私聊
                                std::string to_name = "", to_msg = "";
                                int i;
                                for(i=3; i<msg.length(); ++i) {
                                    if(msg[i] == '|')
                                        break;
                                    else
                                        to_name += msg[i];
                                }
                                for(i=i+1; i<msg.length(); ++i) 
                                    to_msg += msg[i];
                                if(to_msg == "") {
                                    SendMsg("Please Input Like \"to|user|msg\"", nullptr, _SockMap[from_sock]);
                                }
                                else if(_UserMap.count(to_name) == 0) {
                                    SendMsg("User Not Found", nullptr, _SockMap[from_sock]);
                                }
                                else {
                                    SendMsg(to_msg, _SockMap[from_sock], _UserMap[to_name]);
                                }
                            }
                            else {                  //广播
                                SendMsg(msg, _SockMap[from_sock], nullptr);
                            }
                        });
                    }
                }
            }
        }
    }
}

//server发消息
void Server::SendMsg(std::string msg, User* from_user, User* to_user) {
    //获取当前时间
    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
    std::time_t tt = std::chrono::system_clock::to_time_t(now);
    std::stringstream ss;
    ss << std::put_time(std::localtime(&tt), "%T");

    //构造消息格式
    std::string tempmsg;
    if(from_user == nullptr)
        tempmsg = "[ " + ss.str() + " ] " + msg + "\n";
    else
        tempmsg = "[ " + ss.str() + " " + from_user->_name + " ] " + msg + "\n";
    const char* sendmsg = tempmsg.c_str();

    if(to_user == nullptr) {    //广播
        _user_mt.lock();
        for(auto& it : _UserMap) {
            send(it.second->_sock, sendmsg, strlen(sendmsg)+1, 0);
        }
        _user_mt.unlock();
    }
    else {                      //单播
        send(to_user->_sock, sendmsg, strlen(sendmsg)+1, 0);
    }
}

void Server::OnLine(int conn) {
    //创建user对象
    User* user = NewUser(conn, "user "+std::to_string(conn));

    //注册文件描述符
    _epoll->FdAdd(user->_sock);

    //广播上线消息
    std::string msg = user->_name + " is online...";

    _tp->AddTask([this, msg, user](){
        SendMsg(msg, nullptr, nullptr);
        //更新sockmap
        _sock_mt.lock();
        _SockMap[user->_sock] = user;
        _sock_mt.unlock();

        //更新usermap
        _user_mt.lock();
        _UserMap[user->_name] = user;
        _user_mt.unlock();
    });

    std::cout << user->_name << " is online" << std::endl;
}

void Server::OffLine(User* user) {
    //注销文件描述符
    _epoll->FdDel(user->_sock);

    //广播下线消息
    std::string msg = user->_name + " is offline...";

    _tp->AddTask([this, msg, user](){
        //更新sockmap
        _sock_mt.lock();
        _SockMap.erase(user->_sock);
        _sock_mt.unlock();

        //更新usermap
        _user_mt.lock();
        _UserMap.erase(user->_name);
        _user_mt.unlock();
        SendMsg(msg, nullptr, nullptr);
    });

    std::cout << user->_name << " is offline" << std::endl;
    delete user;         //释放user
}

//实例化server对象
Server* NewServer(char* ip, char* port) {
    return new Server(ip, port);
}

5. server.cpp

执行文件

#include "server.h"

int main(int argc, char* argv[])
{
    //创建server对象
    Server* server = NewServer(argv[1], argv[2]);
    
    //运行
    server->Start();

    //释放
    delete server;
    
    return 0;
}
运行截图

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值