目录
前言
在前文多线程的实现方法中,每连接一个客户端则开辟一个线程与之交互,这种情况下,当客户端连接许多时,就需要开辟非常多的线程,这是不现实的。
本文我采用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;
}