asio健壮的异步服务器
引言
asio异步应答服务器存在隐患,就是因为使用了delete删除session对象,而本章使用智能指针防止重复delete对象session,使用智能指针构造成一个伪闭包的状态延长session的生命周期。
智能指针管理Session
我们可以通过智能指针的方式管理Session类,将acceptor接收的链接保存在Session类型的智能指针里。由于智能指针会在引用计数为0时自动析构,所以为了防止其被自动回收,也方便Server管理Session。因为我们后期会做一些重连踢人等业务逻辑,我们在Server类中添加成员变量,该变量为一个map类型,key为Session的uid,value为该Session的智能指针,此时也会增加引用计数
session类
session类定义
- 首先定义一个消息节点,表示接受或者发送的消息
- 定义session类,继承std::enable_shared_from_this<CSession>
- session存储socket,server,消息队列,uuid
- 回调函数中多一个参数,shared_ptr<CSession> _self_shared,传入_self_shared保证引用计数+1,从而防止隐患
1 | |
session实现
1 | |
- start中开始异步读,读回调有send函数(异步写),然后接着开始异步读,继续监听
- send函数开始异步写(如果此时没有在发送消息),写回调中开始异步写,直到全部写完
注意,此方式中:
异步读->读回调(这个里面调用send,开启异步写)->异步读
异步写->写回调->异步写
如果回调函数中不增加引用计数,就会存在风险,假设异步读之后,客户端关闭连接,此时读回调调用send开启异步写,然后读回调开启异步读,会Clearsesson(uuid)两次,这样可能会导致:shared_ptr引用计数变为0,最后一次调用的时候,访问不存在的内存
Cserver类
Cserver类定义
1 | |
Cserver实现
1 | |
易错点
shared_ptr的初始化问题
不能用两个智能指针管理同一块内存,如下用法是错误的:
1 | |
shared_ptr<CSession>(this)生成的新智能指针和this之前绑定的智能指针并不共享引用计数,所以要通过shared_from_this()函数返回智能指针,该智能指针和其他管理这块内存的智能指针共享引用计数:
1 | |
同理,send函数中第一次发送也要绑定shared_from_this()
1 | |
shared_from_this()
shared_from_this() 方法来获取当前对象的 shared_ptr 实例。这样可以确保你得到的 shared_ptr 与最初用于创建当前对象的 shared_ptr 共享相同的控制块。
注意,使用shared_from_this(),必须保证这个类对象是用make_shared方式创建的!!!
流程梳理
- 服务器 (
Server) 初始化一个新的会话 (Session),并为该会话分配一个网络套接字 (Socket)。 - 接受
session后,回调函数中开启监听(async_read_some),然后回调函数中继续开启接受状态(StartAccept()) - 监听到消息,
读回调中调用send(开启写),然后读回调中继续开启监听(async_read_some) - send开启写(
async_write),写回调中继续写,直到写完 - sesson有问题,直接删除即可,整个异步过程的所有函数都有此对象的引用,直到所有函数执行完毕,引用计数才会清0