g++ -std=c++11 -pthread epserver.cpp -o epserver
# -std=c++11 按照c++11的标准编译
# -pthread 引入外部线程库
# EpollServer简称epserver,main主函数在这里面
./epserver 9090
# 启动服务器,指定监听的端口,我这里使用 9090 端口
telnet 127.0.0.1 9090
# telnet是系统自带的通讯协议/远程登陆工具
# 127.0.0.1 是本机的IP地址,9090 是服务器监听的端口,
[root@centOS output]# telnet 127.0.0.1 9090
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
jl g jlfdg // 这一句自己输入的
jl g jlfdg // 这一句是服务器返回来的
告诉对方 梵蒂冈的 jlk // 这一句自己输入的
告诉对方 梵蒂冈的 jlk // 这一句是服务器返回来的
^] // 键入Ctrl+]
telnet> quit // 关闭telnet客户端
Connection closed.
[root@centOS output]#
[root@centOS HTTPserver]# ./httpserver 9090
server main start ...
[INFO][24:55:15][LISTEN_FD:[3] IP:[0.0.0.0]:[9090]][httpserver.cpp][39]
[INFO][24:55:21][COMM_FD:[5] IP:[127.0.0.1]:[37266] join][httpserver.cpp][185]
IP: [127.0.0.1]:[37266]# jl g jlfdg // 客户端发送的
IP: [127.0.0.1]:[37266]# 告诉对方 梵蒂冈的 jlk // 客户端发送的
[INFO][24:55:51][IP:[127.0.0.1]:[37266] left][httpserver.cpp][202]
这是基于C++11来编写的线程池,唯独不支持使用类中的非static函数创建线程,但支持其他任何函数或者类似函数的东西。使用起来非常方便。CSDN\Github中有很多线程池,借一个来用。
// Linux 线程池
#pragma once
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include
#include
#include
#include // 锁
#include // 线程
#include // 条件变量
#include // 接受所有类似函数的东西
#include // 来自未来的值
#include // 原子操作
#include // 智能指针
#include // C++异常
static const size_t THREAD_SIZE = 5; // 默认线程数量
// 线程池
class ThreadPool
{
public:
ThreadPool(size_t);
template <class F, class... Args>
auto enqueue(F &&f, Args &&...args)
-> std::future<typename std::result_of<F(Args...)>::type>;
~ThreadPool();
private:
// need to keep track of threads so we can join them
std::vector<std::thread> workers;
// the task queue
std::queue<std::function<void()>> tasks;
// synchronization
std::mutex queue_mutex;
std::condition_variable condition;
std::atomic<bool> stop;
};
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads = THREAD_SIZE)
: stop(false)
{
threads = threads < 1 ? 1 : threads;
for (size_t i = 0; i < threads; ++i)
workers.emplace_back(
[this]
{
for (;;)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock,
[this]
{ return this->stop || !this->tasks.empty(); });
if (this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
});
}
// add new work item to the pool
template <class F, class... Args>
auto ThreadPool::enqueue(F &&f, Args &&...args)
-> std::future<typename std::result_of<F(Args...)>::type>
{
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
// don't allow enqueueing after stopping the pool
if (stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task]()
{ (*task)(); });
}
condition.notify_one();
return res;
}
// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
this->stop.store(true);
condition.notify_all();
for (std::thread &worker : workers)
worker.join();
}
#endif
线程池的使用方法:
// 线程池使用方法
#include <需要用到的系统头文件>
#include "thread_pool.h"
using namespace std;
int myadd(int a, int b)
{
return a + b;
}
int main()
{
// 初始化 5 个线程
ThreadPool th_pool(5);
// 把需要运行的任务函数的指针、函数的参数,提交进任务队列
future<int> f1 = th_pool.enqueue(myadd, 6, 3);
/*
提交进任务队列后任务会自动执行,如果想获取任务函数的返回值,则使用future 中的get()方法
注意:在主线程中调用get方法,如果线程没有执行完成,
主线程会在这里阻塞,直到线程执行完成,所以要权衡利弊合理使用。
*/
int ret = f1.get();
cout << ret << endl; // 终端会输出数字 9
return 0;
}
进行网络编程需要用到很多头文件,为了图方便,索性把可能用到的头文件都放在这里了。虽然有一些用不到,也有可能会缺少某些头文件,到时候再修改就是了。
其中两个我经常使用的函数: LOG 和 sys_error
// general 通用的东西都放在这里
#pragma once
#ifndef GENERAL_H
#define GENERAL_H
// 网络编程头文件
// Windows环境下头文件
#ifdef _WIN32
#include
#include
#pragma comment(lib,"ws2_32.lib")
// Linux环境下头文件
#else
#include
#include // Linux进程等待
#include // 错误处理
#include // 信号
#include // 提供各种数据类型
#include // 套结字函数和结构
#include // sockaddr_in 结构
#include // IP 地址转换 htons
#include // 域名转换 inet_ntop
#include // I/O控制的函数
#include // socket 等待测试机制
#include // 标准IO,进程等等
#include // 对系统某些文件的访问
#include // epoll IO多路复用
#include // 获取文件信息
#include // open 等关于文件的函数
#include // 零拷贝,在两个内核之间传输数据
#endif
// 跨平台通用头文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const size_t LISTENQ = 1024;
const size_t MAXLINE = 256; // 一行的最大长度
/*
INFO: 表示正常的日志输出,一切按预期运行。
WARNING: 表示警告,该事件不影响服务器运行,但存在风险。
ERROR: 表示发生了某种错误,但该事件不影响服务器继续运行。
FATAL: 表示发生了致命的错误,该事件将导致服务器停止运行。
*/
#define INFO 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
// 输出事件日志 --参数顺序:日志等级/日志信息
// 输出顺序:cout--事件级别--事件发生的时间--事件信息--事件所在的文件--所在的行号--endl;
#define LOG(level, message) Log(#level, message, __FILE__, __LINE__)
// 自己的错误处理包装函数,只有发生严重错误时才调用它
// 先打印错误信息,然后 exit(-1)
void sys_error(const char* ptr)
{
perror(ptr);
exit(-1);
}
// 打印日志 --参数顺序:/日志等级/错误信息/文件名/行号/
static void Log(std::string level, std::string message, std::string file_name, int line)
{
time_t curr_t = time(nullptr); // 获取时间戳
struct tm* curr_tm = gmtime(&curr_t); // 把时间戳转换为 时区为 0 的时间结构
curr_tm->tm_hour += 8; // 小时 +8 才是东八区北京
//const char* myformat = "%Y-%m-%d %H:%M:%S"; // 自己的输出格式
const char* myformat = "%H:%M:%S"; // 定义自己的输出格式
char mytime[25] = { 0 }; // 储存时间
strftime(mytime, 25, myformat, curr_tm); // 把时间结构按照自己的输出格式转换
// 打印顺序:事件级别--事件发生的时间--事件信息--事件所在的文件--所在的行号--\n
std::cout << "[" << level << "][" << mytime << "][" << message
<< "][" << file_name << "][" << line << "]" << std::endl;
}
#endif
封装了一个EpollServer类,是一个单例模式,对外只提供三个接口:
#接口一:单例模式的访问接口,访问这个类时必须调用这个接口
EpollServer::get_server();
#接口二:EpollServer类的初始化,使用之前必须初始化
#,port:监听端口;thread_size:线程池中线程的数量(默认是5)
epoll_server_init(int port, size_t thread_size = 5);
# 接口三:运行接口,不需要参数也没有返回值(因为出现严重错误服务器自己会挂的hhh)
void epoll_run();
# 如何修改服务器的功能?
1# 编写自己的功能函数,例如我编写的是 _server_io() 函数
2# 把自己编写的函数,提交到私有函数 _epoll_run() 中的进线程池,例如:
...code...
size_t comm_fd = revts[i].data.fd;
_th_pool->enqueue(_server_io, comm_fd);
...code...
// Epoll 服务器端
// 编译方法# g++ -std=c++11 -pthread httpserver.cpp -o httpserver
#include
#include "general.h"
#include "thread_pool.h"
#include "http_general.hpp"
using namespace std;
static const size_t SER_PORT = 9090; /* 默认端口号 */
static const size_t EPOLL_SIZE = 1024;
static const size_t REVTS_SIZE = 128; /* 可执行事件容器的大小 */
// 处理HTTP事务
static void httpserver_io(size_t comm_fd);
// Epoll + 线程池 高并发服务器
class EpollServer
{
private:
int _listen_fd; /* 监听套接字 */
string _ipaddr; /* 点分十进制 IP 地址 */
int _port; /* 监听端口号 */
int _epoll_fd; /* epoll 模型 */
shared_ptr<ThreadPool> _th_pool; /* 线程池 */
public:
// key: accept()产生的服务套接字;value:first: 客户端IP地址,second: 客户端的端口号
unordered_map<size_t, pair<string, size_t>> _comm_fd_map;
mutex _map_mutex; /* 在线程中操作映射的时候需要加锁 */
public:
// 单例模式 全局访问接口
static EpollServer& get_server()
{
static EpollServer _epoll_server;
return _epoll_server;
}
// 初始化服务器
void epoll_server_init(int port, size_t thread_size = 5)
{
_listen_fd = -1;
_port = port;
_epoll_fd = -1;
_th_pool = make_shared<ThreadPool>(thread_size);
// 初始化 套接字
_socket_bind_listen();
LOG(INFO, "LISTEN_FD:[" + to_string(_listen_fd) + "] IP:[" + _ipaddr + "]:[" + to_string(_port) + "]");
}
EpollServer(EpollServer&) = delete;
EpollServer(EpollServer&&) = delete;
EpollServer& operator=(EpollServer&) = delete;
EpollServer& operator=(EpollServer&&) = delete;
// 开始运行服务器
void epoll_run()
{
// 初始化 epoll 模型
_epoll_fd = epoll_create(EPOLL_SIZE);
if (-1 == _epoll_fd)
{
LOG(FATAL, "epoll_create error");
sys_error("epoll_create: ");
}
// 在 epoll 中添加监听套接字
_epoll_add(_listen_fd, EPOLLIN | EPOLLET);
while (true)
{
// epoll_wait
epoll_event ep_revts[REVTS_SIZE]; /* 可以执行的集合 */
int ret_wait = epoll_wait(_epoll_fd, ep_revts, REVTS_SIZE, -1);
if (-1 == ret_wait)
{
LOG(FATAL, "epoll_wail error");
sys_error("epoll_wait: ");
}
else if (0 == ret_wait)
{
LOG(WARNING, "epoll_wait timeout");
}
else
{ // 开始正常处理 epoll请求
_epoll_run(ep_revts, ret_wait);
}
}
}
private:
EpollServer() = default;
~EpollServer()
{
if (_listen_fd >= 0)
{
close(_listen_fd);
}
}
// 初始化套接字
void _socket_bind_listen()
{
_listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == _listen_fd)
{
LOG(FATAL, "socket error");
sys_error("socket: ");
}
// 设置端口复用
int opt = 1;
setsockopt(_listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in ser_addr;
socklen_t ser_len = sizeof(ser_addr);
memset(&ser_addr, 0, ser_len);
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = INADDR_ANY;
_ipaddr = inet_ntoa(ser_addr.sin_addr);
// ser_addr.sin_addr.s_addr = inet_addr(m_ip.c_str());
ser_addr.sin_port = htons(_port);
if (0 != bind(_listen_fd, (sockaddr *)&ser_addr, ser_len))
{
LOG(FATAL, "bind error");
sys_error("bind: ");
}
if (0 != listen(_listen_fd, LISTENQ))
{
LOG(FATAL, "listen error");
sys_error("listen: ");
}
}
// 往 epoll 模型中添加任务
void _epoll_add(int fd, uint32_t __events)
{
epoll_event ep_ev;
ep_ev.data.fd = fd;
ep_ev.events = __events;
epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &ep_ev);
}
// 在 epoll 模型中删除任务
void _epoll_del(int fd)
{
epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
}
// 正常处理 epoll 请求
void _epoll_run(epoll_event revts[], size_t ret_wait)
{
for (int i = 0; i < ret_wait; i++)
{
if (revts[i].events & EPOLLIN)
{
if (revts[i].data.fd == _listen_fd)
{ // 等待客户端的接入
int comm_fd = _server_accept();
}
else
{ // 与客户端进行读写数据操作
size_t comm_fd = revts[i].data.fd;
_th_pool->enqueue(httpserver_io, comm_fd);
}
}
}
}
// 等待客户端的接入, 如果接入成功,就建立映射
void _server_accept()
{
sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
memset(&cli_addr, 0, cli_len);
int comm_fd = accept(_listen_fd, (sockaddr *)&cli_addr, &cli_len);
if (comm_fd == -1)
{
LOG(WARNING, "accept error");
return;
}
// 接入成功,就建立映射
char cli_ip[41] = "";
inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, 41);
size_t cli_port = ntohs(cli_addr.sin_port);
string str(cli_ip);
LOG(INFO, "COMM_FD:[" + to_string(comm_fd) + "] IP:[" + str + "]:[" + to_string(cli_port) + "] join");
_comm_fd_map[comm_fd] = make_pair(str, cli_port);
// 有客户端接入成功,在 epoll 中添加这个套接字服务
_epoll_add(comm_fd, EPOLLIN | EPOLLET);
}
};
// EpollServer 的全局访问引用
EpollServer& EP_PTR = EpollServer::get_server();
// 开始与已连接的客户端进行读写,
static void _server_io(size_t comm_fd)
{
char buff[MAXLINE] = "";
int size = read(comm_fd, buff, MAXLINE);
auto& _map = EP_PTR._comm_fd_map[comm_fd];
if (size > 0)
{ // 读入正常,开始处理数据
cout << "IP: [" << _map.first << "]:[" << _map.second << "]# " << buff << endl;
write(comm_fd, buff, size);
}
else
{ // 读入失败,关闭连接
if (size == -1)
{
LOG(ERROR, "COMM_FD:[" + to_string(comm_fd) + "] IP:[" + _map.first
+ "]:[" + to_string(_map.second) + "] read error");
}
close(comm_fd); // 关闭服务套接字
LOG(ERROR, "COMM_FD:[" + to_string(comm_fd) + "] IP:[" + _map.first
+ "]:[" + to_string(_map.second) + "] left");
{
lock_guard<mutex> lock(EP_PTR._map_mutex);
EP_PTR._comm_fd_map.erase(comm_fd); // 删除映射
_epoll_del(comm_fd); /* 处理完成,在epoll中删除这个套接字服务 */
}
}
}
int main(int argc, char **args)
{
cout << "server main start ...\n";
if (argc != 2)
{
cout << "Please cin$$ ./httpserver port" << endl;
cout << "For example$ ./httpserver 9090" << endl;
return -1;
}
EpollServer& ptr = EpollServer::get_server();
ptr.epoll_server_init(stoi(args[1]), 5);
ptr.epoll_run();
cout << "server main end ...\n";
return 0;
}
epoll聊天室烂大街hhhhh