遵义住房和城乡建设局官方网站银川市建设厅网站

当前位置: 首页 > news >正文

遵义住房和城乡建设局官方网站,银川市建设厅网站,做女装的看哪个网站好,wordpress php设置六、day6 今天学习如何利用C11模拟伪闭包实现连接的安全回收#xff0c;之前的异步服务器为echo模式#xff0c;但存在安全隐患#xff0c;在极端情况下客户端关闭可能会导致触发写和读回调函数#xff0c;二者都进入错误处理逻辑#xff0c;进而造成二次析构。今天学习如…六、day6 今天学习如何利用C11模拟伪闭包实现连接的安全回收之前的异步服务器为echo模式但存在安全隐患在极端情况下客户端关闭可能会导致触发写和读回调函数二者都进入错误处理逻辑进而造成二次析构。今天学习如何通过C11智能指针构造伪闭包状态将智能指针传给函数对象如果函数对象不消失该指针也不会消失延长session的生命周期不会发生二次析构的风险。 1智能指针管理Server 通过智能指针的方式管理Session类将acceptor接收的链接保存在Session类型的智能指针里。由于智能指针会在引用计数为0时自动析构所以为了防止其被自动回收也方便Server管理Session因为我们后期会做一些重连踢人等业务逻辑我们在Server类中添加成员变量该变量为一个map类型key为Session的uidvalue为该Session的智能指针。 class Server { private:void start_accept(); // 启动一个acceptor// 当acceptor接收到连接后启动该函数void handle_accept(std::shared_ptrSession new_session, const boost::system::error_code error);boost::asio::io_context _ioc;tcp::acceptor _acceptor;std::mapstd::string, std::shared_ptrSession _sessions; public:Server(boost::asio::io_context ioc, short port);void ClearSession(std::string uuid); };通过Server中的_sessions这个map管理链接可以增加Session智能指针的引用计数只有当Session从这个map中移除后Session才会被释放。所以在接收连接的逻辑里将Session放入map。 但这里产生了一个问题为什么new_session在}结束后仍不结束而是bind后计数加一这个问题在文章后面回答。 void Server::start_accept() {// make_shared分配并构造一个 std::shared_ptr,_ioc, this是传给Session的参数std::shared_ptrSession new_session std::make_sharedSession(_ioc, this);// 开始一个异步接受操作当new_session的socket与客户端连接成功时调用回调函数handle_accept// 为什么new_session在右括号结束后仍不结束而是bind后计数加一_acceptor.async_accept(new_session-Socket(), std::bind(Server::handle_accept, this, new_session,std::placeholders::_1)); }// 当handle_accept触发时也就是start_accept的回调函数被触发当该回调函数结束后从队列中移除后new_session的引用计数减一 void Server::handle_accept(std::shared_ptrSession new_session, const boost::system::error_code error) {// 如果没有错误error 为 false调用 new_session-Start() 来启动与旧客户端的会话if (!error) {new_session-Start();_sessions.insert(std::make_pair(new_session-GetUuid(), new_session));}else // 无论当前连接是否成功都重新调用 start_accept()以便服务器能够继续接受下一个新客户端的连接请求。// 服务器始终保持在监听状态随时准备接受新连接start_accept(); }StartAccept函数中虽然new_session是一个局部变量但是通过bind操作将new_session作为数值传递给bind函数而bind函数返回的函数对象内部引用了该new_session所以引用计数增加1这样保证了new_session不会被释放。在HandleAccept函数里调用session的start函数监听对端收发数据并将session放入map中保证session不被自动释放。 此外需要封装一个释放函数将session从map中移除当其引用计数为0则自动释放 void Server::ClearSession(std::string uuid) {_sessions.erase(uuid); }2Session的uuid session的uuid可以通过boost提供的生成唯一id的函数获得也可以自己实现雪花算法 void Session::HandleWrite(const boost::system::error_code error) {if (!error) {std::lock_guardstd::mutex lock(_send_lock);_send_que.pop();if (!_send_que.empty()) {auto msgnode _send_que.front();boost::asio::async_write(_socket, boost::asio::buffer(msgnode-_data, msgnode-_max_len),std::bind(CSession::HandleWrite, this, std::placeholders::_1));}}else {std::cout handle write failed, error is error.what() endl;_server-ClearSession(_uuid);} } void Session::HandleRead(const boost::system::error_code error, size_t bytes_transferred){if (!error) {cout read data is _data endl;//发送数据Send(_data, bytes_transferred);memset(_data, 0, MAX_LENGTH);_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2));}else {std::cout handle read failed, error is error.what() endl;_server-ClearSession(_uuid);} }但以上操作仍有造成二次析构的隐患 正常情况下上述服务器运行不会出现问题但是当我们像day5一样模拟在服务器要发送数据前打个断点此时关闭客户端在服务器就会先触发写回调函数的错误处理再触发读回调函数的错误处理这样session就会两次从map中移除因为map中key唯一所以第二次map判断没有session的key就不做移除操作了。 但是这么做还是会有崩溃问题因为第一次在session写回调函数中移除sessionsession的引用计数就为0了调用了session的析构函数这样在触发session读回调函数时此时session的内存已经被回收了自然会出现崩溃的问题。解决这个问题可以利用智能指针引用计数和bind的特性实现一个伪闭包的机制延长session的生命周期。 3构造伪闭包 思路 利用智能指针被复制或使用引用计数加一的原理保证内存不被回收bind操作可以将值绑定在一个函数对象上生成新的函数对象如果将智能指针作为参数绑定给函数对象那么智能指针就以值的方式被新函数对象使用那么智能指针的生命周期将和新生成的函数对象一致从而达到延长生命的效果。 void headle_read(const boost::system::error_code error, size_t bytes_transferred,std::shared_ptrSession _self_shared);void haddle_write(const boost::system::error_code error, std::shared_ptrSession _self_shared);以haddle_write举例在bind时传递_self_shared指针增加其引用计数这样_self_shared的生命周期就和async_write的第二个参数(也就是asio要求的回调函数对象headle_read)生命周期一致了。 void Session::haddle_write(const boost::system::error_code error, std::shared_ptrSession _self_shared) {if (!error) {memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(Session::headle_read, this, std::placeholders::_1, std::placeholders::_2, _self_shared));}else {cout write error error.value() endl;_server-ClearSession(_uuid);} }同理headle_read内部也实现了类似的绑定 void Session::headle_read(const boost::system::error_code error, size_t bytes_transferred,std::shared_ptrSession _self_shared) {if (!error) {cout server receive data is _data endl;boost::asio::async_write(_socket, boost::asio::buffer(_data, bytes_transferred),std::bind(Session::haddle_write, this, std::placeholders::_1, _self_shared));}else {cout read error endl;_server-ClearSession(_uuid);} }除此之外在第一次绑定读写回调函数的时候要传入智能指针的值但是要注意传入的方式不能用两个智能指针管理同一块内存如下用法是错误的 void Session::Start() {memset(_data, 0, max_length); // 缓冲区清零// 从套接字中读取数据并绑定回调函数headle_read_socket.async_read_some(boost::asio::buffer(_data, max_length),// 这里可以将shared_ptrSession(this)给bind绑定吗// 不可以会造成多个智能指针绑定同一块内存的问题std::bind(Session::headle_read, this, std::placeholders::_1, std::placeholders::_2,shared_ptrCSession(this))); }shared_ptrCSession(this)生成的新智能指针和this之前绑定的智能指针new_session并不共享引用计数所以要通过shared_from_this()函数返回智能指针该智能指针和其他管理这块内存的智能指针共享引用计数。 void Session::Start() {memset(_data, 0, max_length); // 缓冲区清零// 从套接字中读取数据并绑定回调函数headle_read_socket.async_read_some(boost::asio::buffer(_data, max_length),// 这里可以将shared_ptrSession(this)给bind绑定吗// 不可以会造成多个智能指针绑定同一块内存的问题std::bind(Session::headle_read, this, std::placeholders::_1, std::placeholders::_2,shared_from_this())); }shared_from_this()函数并不是session的成员函数要使用这个函数需要继承std::enable_shared_from_thisSession class Session:public std::enable_shared_from_thisSession { private:tcp::socket _socket; // 处理客户端读写的套接字enum { max_length 1024 };char _data[max_length]; // headle回调函数void headle_read(const boost::system::error_code error, size_t bytes_transferred,std::shared_ptrSession _self_shared);void haddle_write(const boost::system::error_code error, std::shared_ptrSession _self_shared);std::string _uuid;Server* _server; public:Session(boost::asio::io_context ioc, Server* server) : _socket(ioc), _server(server){// random_generator是函数对象加()就是函数再加一个()就是调用该函数boost::uuids::uuid a_uuid boost::uuids::random_generator()();_uuid boost::uuids::to_string(a_uuid);}tcp::socket Socket() { return _socket; }const std::string GetUuid() const { return _uuid; }void Start();};再次测试链接可以安全释放并不存在二次释放的问题。可以在析构函数内打印析构的信息发现只析构一次 1. 为什么new_session在}结束后仍不结束而是bind后计数加一 new_session通过bind绑定时new_session的计数就会加一所以在bind后new_session的生命周期和新构造函数的生命周期相同因为新生成的函数对象引用了new_sessionnew_session通过值传递的方式被复制构造函数使用。所以只要新构造的bind回调函数没有被调用、移除new_session的生命周期就始终存在所以new_session不会随着}的结束而释放。 bind中的shared_from_this()通过自己生成一个与外界智能指针共享相同引用计数的指针保证Cession不被释放或者异常释放因为有可能回调函数还没有调用时引用Session的应用计数便已经为0此时如果回调函数被调用就会发现Session已经被释放。所以为了防止Session被释放需要生成一个智能指针使得Session的引用计数加一并传给回调函数这样能保证回调函数在没有调用之前一直占用一个Session的引用计数Session就不会被释放就达成了一个延时的闭包效果。 总结使用 std::bind 来绑定 new_session 作为回调参数时引用计数也会加一因为 std::bind 内部会将对象拷贝到新生成的函数对象中。这意味着直到回调函数执行完毕且不再被引用时new_session 的生命周期才会结束所以在 } 结束后 new_session 不会被立即销毁。 2. 为什么session执行start函数启动异步操作后会立即将session插入至map种而不是等异步操作执行完返回后才将session插入map new_session-Start(); _sessions.insert(std::make_pair(new_session-GetUuid(), new_session));代码执行顺序 1调用 new_session-Start() 当 Start() 被调用时它会启动一些异步操作如异步读取但是这些异步操作并不会阻塞当前的执行流。它们只是注册了一个回调函数并告诉操作系统在相关事件发生如数据到达、连接完成等时触发回调。所以new_session-Start() 启动异步读取操作async_read_some后控制权立即返回到 handle_accept() 中而不会等待异步操作完成。 2插入 _sessions new_session-Start() 启动异步操作后下一条语句 _sessions.insert(…) 会立即执行。由于 insert 操作是同步的服务器将 new_session 立即插入到 _sessions 容器中这确保 new_session 在异步操作执行期间不会被销毁。 综上异步操作是非堵塞的调用 new_session-Start() 后程序并不会等待异步操作如 async_read_some完成。这些异步操作会在某个事件发生后如客户端发送数据触发注册的回调函数但它们本身不会阻塞代码执行。 3.智能指针new_session的计数增减过程 1在 Server::start_accept() 中创建 new_session 引用计数1 std::shared_ptrSession new_session std::make_sharedSession(_ioc, this);2绑定到异步接受操作同理异步读、异步写都会绑定new_session只有当异步读写结束之后new_session的引用计数才会减一引用计数2 _acceptor.async_accept(new_session-Socket(), std::bind(Server::handle_accept, this, new_session, std::placeholders::_1));因为 std::bind 创建了一个闭包它持有对 new_session 的副本并将其存储起来直到异步操作完成并调用回调函数 handle_accept 3在 Server::handle_accept() 中 void Server::handle_accept(std::shared_ptrSession new_session, const boost::system::error_code error) {if (!error) {new_session-Start(); // 启动会话_sessions.insert(std::make_pair(new_session-GetUuid(), new_session)); // 插入到_sessions中}start_accept(); // 继续等待新的客户端连接 }当 handle_accept() 被调用时new_session 被传递给该函数作为一个局部的 std::shared_ptr。此时引用计数增加 1变为 3。调用 new_session-Start() 后服务器将 new_session 插入到 _sessions 容器中引用计数增加 1变为 4。此时new_session 的引用计数如下 异步操作 async_accept 中持有一个副本。传递给 handle_accept 时有一个副本。存储在 _sessions 容器中有一个副本。 4调用 Session::Start() 并启动异步读取操作 void Session::Start() {_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(Session::headle_read, this, std::placeholders::_1, std::placeholders::_2, shared_from_this())); }在 Start() 函数中调用 async_read_some() 时将 shared_from_this() 传递给回调函数 std::bind创建了另一个 std::shared_ptrSession引用计数增加 1变为 5。shared_from_this() 确保了回调函数中使用的 Session 对象不会在异步操作完成前被销毁。因为在headle_read()和haddle_write()函数中的_self_shared参数是通过 shared_from_this() 获得的 std::shared_ptrSession并通过 std::bind 传递到 headle_read 回调函数中这里的new_session引用指针并不会增加因为_self_shared始终都只是同一个所以在异步操作完成前session都不会被销毁因为引用指针是通过shared_from_this增加的在异步完成前引用不会减一。 5异步读取完成后调用 headle_read() void Session::headle_read(const boost::system::error_code error, size_t bytes_transferred,std::shared_ptrSession _self_shared) {if (!error) {boost::asio::async_write(_socket, boost::asio::buffer(_data, bytes_transferred),std::bind(Session::haddle_write, this, std::placeholders::_1, _self_shared));} else {_server-ClearSession(_uuid);} }引用计数仍然是 5 在 headle_read 函数中_self_shared 是 shared_from_this() 传递过来的 std::shared_ptrSession此时 new_session 的引用计数保持不变。如果读取操作成功继续绑定 async_write 操作并传递 std::shared_ptrSession 给回调函数 haddle_write。如果发生错误调用 _server-ClearSession(_uuid)从 _sessions 中删除 new_session引用计数减少 1变为 4。 6异步写入完成后调用 haddle_write() void Session::haddle_write(const boost::system::error_code error, std::shared_ptrSession _self_shared) {if (!error) {_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(Session::headle_read, this, std::placeholders::_1, std::placeholders::_2, _self_shared));} else {_server-ClearSession(_uuid);} }引用计数保持不变 写入操作完成后再次启动新的异步读取操作依然使用 _self_shared 传递给 async_read_some保持 new_session 的引用计数不变。如果写入操作失败调用 _server-ClearSession(_uuid)从 _sessions 中移除 new_session引用计数减少 1。 7客户端断开连接或发生错误时 void Server::ClearSession(std::string uuid) {_sessions.erase(uuid); }引用计数减少 当客户端断开连接或发生错误时ClearSession() 被调用从 _sessions 容器中删除 new_session。这导致引用计数减少 1。此时如果没有其他异步操作绑定 new_session并且所有与 new_session 相关的回调函数都已经执行完毕那么引用计数最终会减少到 0。当所有的异步操作完成new_session 不再被任何地方持有引用计数归零此时 std::shared_ptr 自动销毁 Session 对象释放其占用的内存。 4.为什么“当 handle_accept() 被调用时new_session 被传递给该函数作为一个局部的 std::shared_ptr。此时引用计数增加 1变为 3”但是“在 headle_read 函数中_self_shared 是 shared_from_this() 传递过来的 std::shared_ptrSession此时 new_session 的引用计数保持不变。”new_session 同样被传递给该函数但为什么这里的计数不加一 第一种情况handle_accept 中传递 new_session void Server::handle_accept(std::shared_ptrSession new_session, const boost::system::error_code error) {if (!error) {new_session-Start(); // 启动会话_sessions.insert(std::make_pair(new_session-GetUuid(), new_session)); // 插入到_sessions中}start_accept(); // 继续等待新的客户端连接 }传递过程handle_accept 函数接收的是 std::shared_ptrSession也就是说在调用 handle_accept 时会将 new_session 作为参数按值传递。为什么计数增加在按值传递时new_session 被拷贝了一份这份拷贝的 std::shared_ptr 在函数内部存在它持有同一个 Session 对象。这种拷贝操作会增加引用计数因此当 handle_accept 被调用时new_session 的引用计数增加 1。总结因为 new_session 是按值传递的std::shared_ptr 的拷贝操作导致引用计数增加。 第二种情况headle_read 中传递 _self_shared void Session::headle_read(const boost::system::error_code error, size_t bytes_transferred,std::shared_ptrSession _self_shared) {if (!error) {} }传递过程_self_shared 是通过 shared_from_this() 获得的 std::shared_ptrSession并通过 std::bind 传递到 headle_read 回调函数中。为什么计数不增加这里的关键在于 _self_shared 并不是按值传递一个新的 std::shared_ptr而是通过 std::bind 传递的已经存在的 std::shared_ptr即 shared_from_this() 返回的那个 shared_ptr。传递的是同一个 std::shared_ptr 对象由于 std::bind 是按引用或按值传递该 shared_ptr 对象取决于如何绑定回调函数接收到的是一个已经存在的 std::shared_ptr而不是新拷贝的 std::shared_ptr。因此这个传递操作并不会创建新的 shared_ptr 对象因此不会增加引用计数。_self_shared 是通过 参数传递 进入 headle_read 函数的而不是像 new_session 那样通过 std::bind 进行传递按值传递。因此在这个函数的参数传递过程中不再进一步增加引用计数。换句话说_self_shared 的引用计数已经通过之前的 shared_from_this() 加一所以这里不再加一。总结在 headle_read 中传递的是 shared_from_this() 已经创建的 std::shared_ptr 的引用传递过程中没有创建新的 shared_ptr 对象因此引用计数保持不变