服务器搭建在linux环境下,为处理高并发,加入epoll模型来接收,客户端的监听以及数据接收;
通过对收包类型的判断用工厂模式来进行不同任务的处理;为啦实现每个模块的隔离,和代码的扩展性,将服务端套接字的类ServerSocket中的服务端套接字m_server和epoll树返回到主函数中进行处理,结构设计有点问题;此程序只设计啦两个简单的心跳检测和登陆请求模块,如需加入其他功能继承BasicWorke类,重写其中去函数可实现自定义功能。
以下代码基本都有注释;
ServerSocket:服务端套接字类;
m_struct.h : 收发包数据结构体;
TaskQueue:线程池任务队列类;
ThreadPool:线程池类; 线程池用啦b站大佬 “爱编程的大炳”的线程池代码,进行修改增加任务参数。
BasicWorke:实现不同任务模块的基类;
HeartCheck:心跳检测,回发心跳包类;(继承于BasicWorke);
UpDataWorke:请求登陆类;(继承于BasicWorke);
1.ServerSocket.h服务端套接字类代码:
#pragma once
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <pthread.h>
using namespace std;
class ServerSocket
{
public:
ServerSocket();
virtual ~ServerSocket();
bool init();
int &getepoll();
int& getserver();
private:
int m_server;
int epoll;
};
ServerSocket.cpp服务端套接字类代码:
#include "ServerSocket.h"
ServerSocket::ServerSocket()
{
if (init()) {
cout << "socket init ok" << endl;
}
else{
cout << "socket init fail" << endl;
}
}
ServerSocket::~ServerSocket()
{
close(m_server);
}
bool ServerSocket::init()
{
m_server = socket(AF_INET, SOCK_STREAM, 0);
if (m_server < 0)
{
cout << "socket error" << endl;
return false;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET; // IPv4
addr.sin_port = htons(11111); // port
addr.sin_addr.s_addr = INADDR_ANY; // IP address
if (bind(m_server, (struct sockaddr*)&addr, sizeof(addr)) < 0) { //绑定本地IP端口
cout << "bind fail:" << endl;
return false;
}
if (listen(m_server, 10)) { //循环监听每次监听10个客户端连接
cout << "listen fail:"<< endl;
return false;
}
epoll = epoll_create(100); //初始化epoll树;
if (epoll == -1) {
return false;
}
struct epoll_event ev;
ev.events = EPOLLIN; // 检测lfd读读缓冲区是否有数据
ev.data.fd = m_server;
int ret = epoll_ctl(epoll, EPOLL_CTL_ADD, m_server, &ev);//将监听任务添加在epoll树上;
while (ret == -1) { //检查是否添加成功
ret = epoll_ctl(epoll, EPOLL_CTL_ADD, m_server, &ev);//未添加成功循环添加;
}
return true;
}
int& ServerSocket::getserver()
{
return m_server;
}
int& ServerSocket::getepoll()//将epoll和客户端m_server返回主函数;
{
return epoll;
}
2.m_struct.h : 收发包数据结构体;
#ifndef M_STRUCT_H
#define M_STRUCT_H
enum TypeInfo //心跳检测枚举体
{
HEART_CHECK_REQ,
HEART_CHECK_RES,
};
struct Head //数据包包头
{
int type;
int len;
};
struct HeartCheckReq // 心跳检测包发包
{
Head head;
HeartCheckReq() {
head.type = HEART_CHECK_REQ;
head.len = sizeof(HeartCheckReq);
}
};
struct HeartCheckRes //心跳检测回包
{
Head head;
HeartCheckRes() {
head.type = HEART_CHECK_RES;
head.len = sizeof(HeartCheckRes);
}
};
struct RequestLogin //请求登陆数据包;
{
Head head;
RequestLogin(){
head.type = 1;
head.len = sizeof(RequestLogin);
}
char buf[32];
};
#endif // M_STRUCT_H
3.TaskQueue.h:线程池任务队列类头文件代码
#pragma once
#include <queue>
#include <pthread.h>
using callback = void(*)(void* arg,int* client);
//任务结构体
template<typename T>
struct Task
{
Task()
{
function = nullptr;
arg = nullptr;
client = nullptr;
}
Task(callback f, void* arg,int *client)
{
function = f;
this->arg = (T*)arg;
this->client = client;
}
callback function;
T* arg;
int* client;
};
TaskQueue.cpp:线程池任务队列类源文件代码
#include "TaskQueue.h"
template<typename T>
TaskQueue<T>::TaskQueue()
{
pthread_mutex_init(&m_mutex, NULL);
}
template<typename T>
TaskQueue<T>::~TaskQueue()
{
pthread_mutex_destroy(&m_mutex);
}
template<typename T>
void TaskQueue<T>::addTask(Task<T> task)
{
pthread_mutex_lock(&m_mutex);
m_taskQ.push(task);
pthread_mutex_unlock(&m_mutex);
}
template<typename T>
void TaskQueue<T>::addTask(callback f, void* arg,int *client)
{
pthread_mutex_lock(&m_mutex);
m_taskQ.push(Task<T>(f, arg, client));
pthread_mutex_unlock(&m_mutex);
}
template<typename T>
Task<T> TaskQueue<T>::taskTask()
{
Task<T> task;
pthread_mutex_lock(&m_mutex);
if (!m_taskQ.empty())
{
task = m_taskQ.front();
m_taskQ.pop();
}
pthread_mutex_unlock(&m_mutex);
return task;
}
4.ThreadPool.h:线程池类头文件代码;
#pragma once
#include "TaskQueue.h"
#include "TaskQueue.cpp"
template<typename T>
class ThreadPool
{
private:
//任务队列
TaskQueue<T>* taskQ;
pthread_t managerID; //管理者线程ID
pthread_t* threadIDs; //工作线程ID
int minNum; //最小线程数量
int maxNum; //最大线程数量
int busyNum; //忙的线程个数
int liveNum; //存活的线程个数
int exitNum; //要销毁的线程个数
pthread_mutex_t mutexPool; //锁整个线程池
pthread_cond_t notEmpty; //任务队列是否为空了
bool shutdown; //是不是要销毁线程池,销毁为1,不销毁为0
static const int NUMBER = 2;
public:
//创建线程池并初始化
ThreadPool(int min, int max);
//销毁线程池
~ThreadPool();
//给线程池添加任务
void addTask(Task<T> task);
//获取线程池中工作的线程个数
int getBusyNum();
//获取线程池中活着的线程个数
int getAliveNum();
private:
//工作的线程(消费者线程)任务函数
static void* worker(void* arg);
//管理者线程任务函数
static void* manager(void* arg);
//单个线程退出
void threadExit();
};
ThreadPool.cpp:线程池类源文件代码;
#include "ThreadPool.h"
#include <iostream>
#include <string.h>
#include <string>
#include <unistd.h>
using namespace std;
template<typename T>
ThreadPool<T>::ThreadPool(int min, int max)
{
do
{
//实例化任务队列
taskQ = new TaskQueue<T>;
if (taskQ == nullptr)
{
cout << "malloc taskQ fail...\n";
break;
}
threadIDs = new pthread_t[max];
if (threadIDs == nullptr)
{
cout << "malloc threadIDs fail...\n";
break;
}
memset(threadIDs, 0, sizeof(pthread_t) * max);
minNum = min;
maxNum = max;
busyNum = 0;
liveNum = min; //和最小个数相等
exitNum = 0;
if (pthread_mutex_init(&mutexPool, NULL) != 0 ||
pthread_cond_init(¬Empty, NULL) != 0)
{
cout << "mutex or condition init fail...\n";
break;
}
shutdown = false;
//创建管理者线程
//第三个参数在类内部,需要传入已经存在的地址(类实例化才有)
//因此换成静态成员函数或友元函数;传入this是为了可以
//访问其它成员属性和方法,静态方法只能访问静态的属性
pthread_create(&managerID, NULL, manager, this);
//创建工作线程
for (int i = 0; i < min; ++i)
{
pthread_create(&threadIDs[i], NULL, worker, this);
}
return;
} while (0);
//创建失败,释放资源
if (threadIDs) delete[] threadIDs;
}
template<typename T>
ThreadPool<T>::~ThreadPool()
{
//关闭线程池
shutdown = true;
//阻塞回收管理者线程池
pthread_join(managerID, NULL);
//唤醒阻塞的工作线程(消费者线程)
for (int i = 0; i < liveNum; ++i)
{
pthread_cond_signal(¬Empty);
}
//释放堆内存
if (taskQ)
{
delete taskQ;
}
if (threadIDs)
{
delete[]threadIDs;
}
pthread_mutex_destroy(&mutexPool);
pthread_cond_destroy(¬Empty);
}
template<typename T>
void ThreadPool<T>::addTask(Task<T> task)
{
if (shutdown)//已满
{
return;
}
//添加任务
taskQ->addTask(task);
pthread_cond_signal(¬Empty);//唤醒工作线程
}
template<typename T>
int ThreadPool<T>::getBusyNum()
{
pthread_mutex_lock(&mutexPool);
int busyNum = this->busyNum;
pthread_mutex_unlock(&mutexPool);
return busyNum;
}
template<typename T>
int ThreadPool<T>::getAliveNum()
{
pthread_mutex_lock(&mutexPool);
int aliveNum = this->liveNum;
pthread_mutex_unlock(&mutexPool);
return aliveNum;
}
template<typename T>
//工作线程
void* ThreadPool<T>::worker(void* arg)
{
//利用传入的this类调用该成员属性和方法
ThreadPool* pool = static_cast<ThreadPool*>(arg);
while (true)
{
pthread_mutex_lock(&pool->mutexPool);
//当前任务队列是否为空
while (pool->taskQ->taskNumber() == 0 &&!pool->shutdown)
{
//阻塞工作线程
pthread_cond_wait(&pool->notEmpty, &pool->mutexPool);
//判断是否要销毁线程,其实是管理者线程执行这段代码
if (pool->exitNum > 0)
{
pool->exitNum--;
if (pool->liveNum > pool->minNum)
{
pool->liveNum--;
pthread_mutex_unlock(&pool->mutexPool);
pool->threadExit();
}
}
}
//判断线程池是否关闭了
if (pool->shutdown)
{
pthread_mutex_unlock(&pool->mutexPool);//释放锁
pool->threadExit();//线程退出
}
//从任务队列中取出一个任务
Task<T> task = pool->taskQ->taskTask();
pool->busyNum++;
//解锁
pthread_mutex_unlock(&pool->mutexPool);
cout << "thread " << to_string(pthread_self()) << " start working...\n";
task.function(task.arg,task.client);//执行任务函数
task.arg = nullptr;
delete task.arg;
cout << "thread " << to_string(pthread_self()) << " end working...\n";
pthread_mutex_lock(&pool->mutexPool);
pool->busyNum--;
pthread_mutex_unlock(&pool->mutexPool);
}
return NULL;
}
template<typename T>
void* ThreadPool<T>::manager(void* arg)
{
ThreadPool* pool = static_cast<ThreadPool*>(arg);
while (!pool->shutdown)
{
//每隔3s检测一次
sleep(3);
//取出线程池中任务的数量、当前线程的数量、忙的线程的数量
pthread_mutex_lock(&pool->mutexPool);;
int queueSize = pool->taskQ->taskNumber();
int liveNum = pool->liveNum;
int busyNum = pool->busyNum;
pthread_mutex_unlock(&pool->mutexPool);
//添加线程
//任务的个数>存活的线程个数&&存活的线程数<最大线程数
if (queueSize > liveNum-busyNum && liveNum < pool->maxNum)
{
pthread_mutex_lock(&pool->mutexPool);
int counter = 0;//默认最大增加2个工作线程
for (int i = 0; i < pool->maxNum && counter < NUMBER && pool->liveNum < pool->maxNum; ++i)
{
if (pool->threadIDs[i] == 0)
{
pthread_create(&pool->threadIDs[i], NULL, worker, pool);
counter++;
pool->liveNum++;
}
}
pthread_mutex_unlock(&pool->mutexPool);
}
//销毁线程
//忙的线程*2<存活的线程数&&存活的线程>最小线程数
if (busyNum * 2 < liveNum && liveNum > pool->minNum)
{
pthread_mutex_lock(&pool->mutexPool);
pool->exitNum = NUMBER;
pthread_mutex_unlock(&pool->mutexPool);
//让工作的线程自杀
for (int i = 0; i < NUMBER; ++i)
{
pthread_cond_signal(&pool->notEmpty);//唤醒阻塞的线程
}
}
}
return NULL;
}
template<typename T>
void ThreadPool<T>::threadExit()
{
pthread_t tid = pthread_self();
for (int i = 0; i < maxNum; ++i)
{
if (threadIDs[i] == tid)
{
threadIDs[i] = 0;
cout << "threadExit() called, " << to_string(tid) << " exiting...\n";
break;
}
}
pthread_exit(NULL);//线程退出
}
5.BasicWorke.h:实现不同任务模块的基类头文件代码;
#pragma once
#include<iostream>
using namespace std;
class BasicWorke
{
public:
BasicWorke();
virtual ~BasicWorke();
virtual void worker(char* buf, int* client)=0;
};
BasicWorke.cpp:实现不同任务模块的基类源文件代码;
#include "BasicWorke.h"
BasicWorke::BasicWorke()
{
cout << "BasicWorke" << endl;
}
BasicWorke::~BasicWorke()
{
cout << "~BasicWorke" << endl;
}
6.HeartCheck.h:心跳检测,回发心跳包类头文件;
#include "BasicWorke.h"
#include <iostream>
#include <map>
using namespace std;
class HeartCheck :
public BasicWorke
{
public:
HeartCheck(bool openCheck = true);
virtual ~HeartCheck();
virtual void worker(char* buf, int *client);
void heartCheck();
private:
map<int, int> m_clientSockets;
bool m_openCheck; // 是否开启心跳检测的开关// 客户端套接字及阈值
};
HeartCheck.cpp:心跳检测,回发心跳包类源文件;
#include "HeartCheck.h"
#include "m_struct.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#define HEART_CHECK_TIMES 8 // 心跳检测阈值,次数为 8
pthread_mutex_t heartmap; //心跳检测客户端map集合锁;防止多线程同时修改同一键值对出错。
HeartCheck::HeartCheck(bool openCheck)
:m_openCheck(openCheck)
{
cout << "HeartCheck" << endl;
}
HeartCheck::~HeartCheck()
{
cout << "~HeartCheck" << endl;
}
void HeartCheck::worker(char* buf, int *client)
{
pthread_mutex_trylock(&heartmap);
m_clientSockets[*client] = HEART_CHECK_TIMES;//有新连接发来心跳检测包,重置此链接的阈值;
cout << "recv heart package" << endl;
pthread_mutex_unlock(&heartmap);
HeartCheckRes res;
send(*client, (char*)&res, res.head.len, 0);
}
void HeartCheck::heartCheck()
{
while (m_openCheck) {
for (auto it = m_clientSockets.begin(); it != m_clientSockets.end(); ) {
pthread_mutex_trylock(&heartmap);
it->second--; // 阈值递减
pthread_mutex_unlock(&heartmap);
cout << "client:" << it->first << "yuzhi value:" << it->second << endl;
if (it->second == 0) { //发现无用长连接;从map中去除此连接;
cout << "find long unuse connect,client:" << it->first << endl;
pthread_mutex_trylock(&heartmap);
close(it->first); // 关闭客户端套接字
// 删除
m_clientSockets.erase(it++); // it++ 防止迭代器失效
pthread_mutex_unlock(&heartmap);
}
else {
it++;
}
}
sleep(5); // 间隔5秒轮询一次
}
}
7.UpDataWorke.h:请求登陆类头文件;
#pragma once
#include "BasicWorke.h"
#include "m_struct.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
using namespace std;
class UpDataWorke :
public BasicWorke
{
public:
UpDataWorke();
virtual ~UpDataWorke();
virtual void worker(char* buf, int* client);
};
UpDataWorke.cpp:请求登陆类源文件
#include "UpDataWorke.h"
#include <string.h>
UpDataWorke::UpDataWorke()
{
}
UpDataWorke::~UpDataWorke()
{
}
void UpDataWorke::worker(char* buf, int* client)
{
struct RequestLogin* login = (struct RequestLogin*)buf;
cout << "客户端:" << login->buf << endl;
strcpy(login->buf, "允许登陆,成功");
send(*client, (char*)login, login->head.len, 0);
}
主函数main.cpp;
#include <stdio.h>
#include <pthread.h>
#include <iostream>
#include "BasicWorke.h"
#include "HeartCheck.h"
#include "UpDataWorke.h"
#include "ThreadPool.h"
#include "ThreadPool.cpp"
#include "ServerSocket.h"
#include "m_struct.h"
#include <unistd.h>
#include <stdlib.h>
using namespace std;
static HeartCheck heartCheck; //实例化心跳检测的类;
ThreadPool<char*> pool(3, 10);//初始化线程池;最少3条,最多10条;可根据自己需要进行设置;
void taskFunc(void* arg,int *client) { //线程池添加的任务函数
int type = *(int*)arg;
BasicWorke* basicWorke = nullptr;// 实例化基类BasicWorke使用动态多态去实现不同的实现功能;
if (type == HEART_CHECK_REQ) { //若要添加实现自定义模块派生类,在此进行多态判断;
basicWorke = &heartCheck;//接收客户端心跳包,调用heartCheck并回复心跳包
}
else if (type == 1) {
basicWorke = new UpDataWorke;//当接收到登陆请求时,调用UpDataWorke类实现
}
basicWorke->worker((char*)arg, client);
if (dynamic_cast<HeartCheck*>(basicWorke) == nullptr) { //若不是心跳检测线程则析构;
delete basicWorke;
}
}
void worke(int& client, char* buf) {
pool.addTask(Task<char*>(taskFunc, buf, &client));
}
void* heatcheck1(void* arg) {
heartCheck.heartCheck();
return NULL;
}
int main()
{
ServerSocket server; //启动客户端
int m_server = server.getserver();
int epoll = server.getepoll(); //接收epoll模型与服务端套接字
pthread_t heartCheck_pthread; //单起一条线程用于一直心跳检测;
pthread_create(&heartCheck_pthread, NULL, heatcheck1, NULL);
pthread_detach(heartCheck_pthread);
struct epoll_event evs[1024];
int size = sizeof(evs) / sizeof(struct epoll_event);
// 持续检测
struct epoll_event ev;
ev.events = EPOLLIN; // 水平触发;
ev.data.fd = m_server;
int ret;
while (1)
{
// 调用一次, 检测一次
int num = epoll_wait(epoll, evs, size, -1);//检测当前epoll树中已经连接的文件描述符(客户端套接字)
for (int i = 0; i < num; ++i)
{
// 取出当前的文件描述符
int curfd = evs[i].data.fd;
// 判断这个文件描述符是不是用于监听的
if (curfd == m_server)
{
// 建立新的连接
int cfd = accept(curfd, NULL, NULL);
// 新得到的文件描述符添加到epoll模型中, 下一轮循环的时候就可以被检测了
//ev.events = EPOLLIN; // 读缓冲区是否有数据
ev.data.fd = cfd;
ret = epoll_ctl(epoll, EPOLL_CTL_ADD, cfd, &ev);
if (ret == -1)
{
perror("epoll_ctl-accept");
exit(0);
}
}
else
{
cout << "have data already" << endl;
// 处理通信的文件描述符
// 接收数据
char buf[1024];
memset(buf, 0, sizeof(buf));
int len = recv(curfd, buf, sizeof(buf), 0);
if (len == 0)
{
printf("客户端已经断开了连接\n");
// 将这个文件描述符从epoll模型中删除
epoll_ctl(epoll, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
}
else if (len > 0) //处理数据包;
{
worke(curfd, buf);
}
else
{
perror("recv");
exit(0);
}
}
}
}
pthread_exit(&heartCheck_pthread);//退出心跳检测线程;
return 0;
}
运行结果;
客户端代码:
client.h头文件代码;
#ifndef CLIENT_H
#define CLIENT_H
#include <QMainWindow>
#include <QTcpSocket>
#include <QTimer>
#include "m_struct.h"
namespace Ui {
class Client;
}
class Client : public QMainWindow
{
Q_OBJECT
public:
explicit Client(QWidget *parent = 0);
~Client();
private slots:
void heartCheckSlot();
void myRead();
void on_pushButton_clicked();
private:
Ui::Client *ui;
int m_checkTimes;
QTcpSocket *m_client;
QTimer *m_heartTimer;
};
#endif // CLIENT_H
client.cpp源文件代码;
#include "client.h"
#include "ui_client.h"
#include <QDebug>
#define HEART_CHECK_TIMES 8 // 心跳阈值
Client::Client(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Client)
{
ui->setupUi(this);
m_client = new QTcpSocket(this);
m_client->connectToHost("192.168.140.128",11111);
m_checkTimes = HEART_CHECK_TIMES; // 设置初始阈值
if(m_client->waitForConnected()){
qDebug()<<"conn ok";
connect(m_client,SIGNAL(readyRead()),this,SLOT(myRead()));
m_heartTimer = new QTimer(this);
connect(m_heartTimer,SIGNAL(timeout()),this,SLOT(heartCheckSlot()));
m_heartTimer->start(5*1000);
}else{
qDebug()<<"conn fail"<<m_client->errorString();
}
}
Client::~Client()
{
delete ui;
}
void Client::heartCheckSlot()
{
HeartCheckReq req;
qDebug()<<m_client->write((char*)&req,req.head.len);
m_checkTimes=m_checkTimes-1; // 阈值递减
qDebug()<<"阈值:"<<m_checkTimes;
if(m_checkTimes <= 0){
// 断线重连
m_client->close();
m_client->connectToHost("192.168.140.128",11111);
if(m_client->waitForConnected()){
m_checkTimes=HEART_CHECK_TIMES;
qDebug()<<"断线重连成功";
}
}
}
void Client::myRead()
{
QByteArray buffer = m_client->readAll();
m_checkTimes = HEART_CHECK_TIMES; // 重置阈值
int type = *(int*)buffer.data();
if(type == HEART_CHECK_RES){
qDebug()<<"收到心跳响应";
}
else if(type==1){
qDebug()<<"登陆成功";
}
}
void Client::on_pushButton_clicked()
{
struct RequestLogin login;
strcpy(login.buf,"请求登陆");
m_client->write((char*)&login,login.head.len);
}
主函数main.cpp
#include "client.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Client w;
w.show();
return a.exec();
}
界面及运行结果:
耐心看完的您麻烦点个关注,thanks;后附完整的服务器和客户端的完成的代码包;