boost:asio 联接管理1

boost::asio 连接管理1

在完成了第一个基于boost::asio的通信服务程序后,回顾一下所用到的概念,参考一些资料。将用一个系列来归纳一下如何通过boost::asio编写高性能TCP服务程序。

本篇从简单的单线程开始,描述如何监听端口,接收连接请求。同时也复用了前面的“优雅的退出” 的代码。

首先main函数创建一个server对象,server对象负责监听本地8888端口,一旦有连接请求,则创建一个connection对象,并调用StartWork开始工作。

下面是代码:

#include <cstdlib>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>

using namespace boost;
using namespace boost::asio;
using ip::tcp;
using namespace std;
using boost::system::error_code;

class Connection {
public:
    Connection(io_service& s): socket(s) {
        
    }
    
    ~Connection() {
        socket.close();
        cout << "~Connection" << endl;
    }
    
    void StartWork() {
        cout << "The new connection object is starting now." << endl;
    }
    
public:
    tcp::socket socket;
};

class Server {
public:

    Server(io_service & s, tcp::endpoint const& listen_endpoint)
    : io_(s), signals_(s), acceptor_(s, listen_endpoint) {
        signals_.add(SIGINT);
        signals_.add(SIGTERM);
#if defined(SIGQUIT)
        signals_.add(SIGQUIT);
#endif
        signals_.async_wait(boost::bind(&Server::Stop, this));
        shared_ptr<Connection> c(new Connection(io_));
        cout << "count1:" << c.use_count() << endl;
        acceptor_.async_accept(c->socket,
                boost::bind(&Server::AfterAccept, this, c, _1));
        cout << "count2:" << c.use_count() << endl;
    }

    void Run() {
        io_.run();
    }

    void AfterAccept(shared_ptr<Connection>& c, error_code const& ec) {
        // Check whether the server was stopped by a signal before this completion
        // handler had a chance to run.
        if (!acceptor_.is_open()) {
            return;
        }
        
        cout << "count3:" << c.use_count() << endl;
        
        if (!ec) {
            c->StartWork();

            shared_ptr<Connection> c2(new Connection(io_));

            acceptor_.async_accept(c2->socket,
                    boost::bind(&Server::AfterAccept, this, c2, _1));
        }
    }

private:

    void Stop() {
        cout << "stop io_service" << endl;
        io_.stop();
    }

private:
    io_service& io_;
    boost::asio::signal_set signals_;
    tcp::acceptor acceptor_;
};

int main(int argc, char** argv) {
    io_service s;

    tcp::endpoint listen_endpoint(tcp::v4(), 8888);

    Server server(s, listen_endpoint);
    server.Run();
    return 0;
}


代码有点长,但是和真正的产品来讲已经很简单了。

通过telnet localhost 8888 命令可以迅速测试一下,会看到连接已经建立,很快又被断开。

程序打印出两行消息:

The new connection object is starting now.
~Connection


这个关系到对象生命周期的问题。

先看Server的构造函数里面,创建了一个Connection对象,用shared_ptr管理,此时引用计数为1,然后在下面一句bind调用时,将c变量保存到bind_t中了,因此引用计数会增加到2, 之后构造函数结束,引用计数减到1.

asio回调bind_t对象的operator()() 的时候最终会调用到AfterAccept函数,c对象作为引用参数传递进来,因此参数传递没有增加引用计数。但是这中间的asio回调机制使得引用计数增加到2. 

然后里面运行了StartWork,StartWork很快返回。当AfterAccept函数退出时,asio的回调机制的清理将引用计数减到1, 而bind_t这个function object对象也被销毁,因此引用计数降到0,所以c的析构函数被调用。

bind内部的细节不去管他,这里只要注意一旦StartWork函数退出,对象就会被销毁。


由于在AfterAccept函数最后总是又调用一次async_accept函数,所以会一直不断的等待新的连接。直到io_service::stop函数被调用,才结束整个程序。

这个简单的例子是一个开始,也说明了连接是如何建立的,连接对象的生命周期是如此脆弱。后面会继续改进。