在前一篇文章中,我们介绍了如何构建一个简单的TCP echo server。
本文继续谈一下如何构建一个可用程度更高一些(更复杂)的server。
Linux平台下的问题
Asio 给出的标准实例,是单个io_context可以多线程run,使用该io_context进行分发回调。
这个模型在window 上的iocp 实现,简直完美,因为接口都是系统api,各个线程等待完成事件都是不需要锁来等待的。锁只需要保护队列即可。
Linux 平台,使用epoll模拟,导致一个contex在多个线程run会有一把大锁直接锁调用。其实多线程run就是不同线程切换run,性能会有损失。但是,另一种做法是用一个io_context进行accept拿到连接,再建立一个io_context池,把连接的处理抛到池中执行。跟默认epoll模型一致,也就可以了。
一个这样的io_context池的实现如下:
1 | typedef std::shared_ptr<boost::asio::io_context> io_context_ptr; |
在这个io_context池的帮助下,我们可以在拿到连接后将连接抛入池中:
1 | void do_accept() |
这种做法有一个小的提升——因为每一个io_context对象都只在一个线程中run,所以socket的读写不再需要加锁了。
平滑重启
与客户端程序不同的是,服务端程序要尽可能的长时间运行,故障时能够自动恢复,并且更新时不能影响服务正在处理的请求。这就产生了平滑重启的功能需求。实际上,网络上比较流行的 HTTP 服务器 Nginx 就支持平滑重启。
平滑重启的原理实际上比较简单,即,当接收到平滑重启(或退出)的信号后,旧的服务关闭监听套接字,处理完所有请求后便退出。而在旧的服务关闭监听套接字后,启动一个新的服务监听套接字,处理新的请求。
Asio提供了signal_set来帮助我们处理应用程序的信号通信。
附录:实现源码