十五、day15
服务器主动退出一直是服务器设计必须考虑的一个方向,旨在能通过捕获信号使服务器安全退出。我们可以通过asio提供的信号机制绑定回调函数即可实现优雅退出。
之前服务器的主函数如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include "CSession.h" #include "CServer.h"
int main() { try { boost::asio::io_context ioc; CServer s(ioc, 10086); ioc.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << '\n'; } boost::asio::io_context io_context; }
|
有两种修改方式,第一种基于condition_variable和lock_quit;第二种基于asio库
1)第一种实现方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include "CSession.h" #include "CServer.h" #include <csignal> #include <thread> #include <mutex>
bool bstop = false; std::condition_variable cond_quit; std::mutex mutex_quit;
void sig_handler(int sig) { if (sig == SIGINT or sig == SIGTERM) { std::unique_lock<std::mutex> lock_quit(mutex_quit); bstop = true; cond_quit.notify_one(); } }
int main() { try { boost::asio::io_context ioc; std::thread net_work_thread([&ioc] { CServer s(ioc, 10086); ioc.run(); }); signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); while (!bstop) { std::unique_lock<std::mutex> lock_quit(mutex_quit); cond_quit.wait(lock_quit); }
ioc.stop(); net_work_thread.join(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << '\n'; } boost::asio::io_context io_context; }
|
- 条件变量、关服标志、锁必须都是全局变量
- 自定义关服回调函数sig_handler,一旦捕获到SIGINT(Ctrl+C触发)或SIGTERM(终止进程信号),系统调用sig_handler函数。在回调函数内首先进行加锁,以免重复操作,然后将关服标志位置为true,并将主线程唤醒,’}’结束后自动解锁
- 启动子线程运行网络服务
- 主线程种设置信号处理函数并将主线程挂起,等待退出信号(循环会占用cpu资源,这里通过挂起避免循环,节省资源)
- 当主线程被唤醒后,退出循环,执行ioc.stop()来停止io_context,并将子线程中的ioc.run()返回
- net_work_thread.join()确保子线程在主线程退出之前先结束运行,防止资源泄露
注:信号处理是异步的,即使主线程在等待条件变量,也可以被信号打断,执行信号处理函数
2)第二种实现方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int main() { try { boost::asio::io_context ioc; boost::asio::signal_set signals(ioc, SIGINT, SIGTERM); signals.async_wait([&ioc](auto, auto) { ioc.stop(); });
CServer s(ioc, 10086); ioc.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << '\n'; } }
|
第二种方式是我推荐的,最优雅最简单。
主要通过asio库的signal_set 函数执行信号处理操作,允许用户在指定的io_context 上异步等待这些信号的到来,并在信号到达时执行相应的回调
- 异步处理: 通过将信号处理与 io_context 结合,程序能够继续处理其他异步任务,而不需要在信号到达时阻塞主线程。
- 多线程支持: 如果程序使用多线程,信号的处理仍然可以有效地协调各个线程的操作。
3)测试
两种方式均能得到以下测试结果

