第13章 网络 Page749~755 asio核心类 ip::tcp::acceptor

4,ip::tcp::acceptor

ip::tcp::socket类提供用于客户端 async_connect()(发起连接)方法,但没有提供服务端

用于assync_accept()(接受连接)的方法;这正对应了客户端和服务端之间的"多对一"关系:

每一个socket都可以主动发起连接,但连接的目标可以是同一个服务端; 倒过来,就是一个

服务端可以接受并拥有多个客户端发起的连接。

ip::tcp::acceptor(接收器)就负责一件事:当有新客户端发起连接请求时,acceptor
决定是否接受。

接受,则建立连接,不接受则连接被扼杀在摇篮里。

ip::tcp::acceptor 类的构造函数的常用版本声明如下:

cpp 复制代码
acceptor(io_service& ios, endpoint_type const& endpoint
                            , bool reuse_addr = true);

服务端的"接受器"当然是一个I/O对象,所以第一个入参是io_service对象。
第二个入参指明当前服务的监听地址endpoint。

最后一个入参指明端口是否可复用,如果不懂什么意思,就取默认值

该构造过程会自动执行服务端的监听(listen)动作

**acceptor"接收器"**最重要的方法

是 async_accept(),它还有一个同步版本accept(),因为很少有不在意

性能的网络服务端应用,所以我们终点讲异步版本

async_accept()又有两个常用版本,二者只差一个入参:

cpp 复制代码
//版本一:三个入参
void async_accept(ip::tcp::socket& peer_socket
                 , endpoint_type& peer_endpoint
				 ,AcceptHandler handler);
				 
//版本二;两个入参(没有peer_endpoint)
void async_accept(ip::tcp::socket& peer_socket
                    , AcceptHandler handler);

版本二不提供第二个入参peer_endpoint, 可视为版本一的简化,我们以版本一作为讲解对象

第一个入参 peer_socket表示来自对端的网络套接字。

当客户端发起连接,它会沿着网线往服务端传来一个C++变量,并且区分为传值和传址两种方式。这个参数作为入参传递给async_accept()函数,此时还没有接受到任何连接请求,因此该变量

只是服务端的程序预备用来存储未来可能有的新连接

第二个入参peer_endpoint和peer_socket类似,同样是需要服务端在函数调用前,事先定义的

一个变量。等到确实有客户端新请求到来是,用它来存储客户端的地址(主机地址+端口号)。

当有新连接请求时,第三个入参所代表的回调操作被触发,该回调原型为:

cpp 复制代码
void handler(const boost::system::error_code& error);

ip::tcp::resolver::async_resolve()的回调函数好歹传回了解析的结果resolver::iterator iterator,

但 ip::tcp::async_accept() 的回调又只是传回是否出错的结果,代表网络连接的那个套接字变量

peer_socket上哪儿去了?那个代表客户端地址的peer_endpoint又上哪儿去了?

在对比asio与licurl的差异时,我们就已经说过了,前者需要我们自行维护好这些数据。之前my_resolver程序在解析出ip地址后,会以客户端的身份,向该地址以及用户输入的端口所代表的目标,尝试发起连接,若成功,就输出"成功。"

DumbServer(哑巴服务器):

DumbServer类只有一个成员数据,即ip::tcp::acceptor。先看构造函数:

cpp 复制代码
class DumbServer
{
public:
    DumbServer(boost::asio::io_service& ios, char const* host
                                , unsigned short port)
        : _acceptor(ios, make_endpoint(host, port))
    {
         cout << "哑巴服务运行在:" 
            << host << " : "<< port << endl;
         cout << "按 Ctrl - C 退出。" << endl;
    }
    
private:
    boost::asio::ip::tcp::acceptor _acceptor;
};

acceptor和其他"I/O类"一样,构造时,io_service入参必须以引用方式传入,因成员"_acceptor"必须在DumbServer初始化列表中完成构造,而它需要一个io_service对象。

第二个入参服务监听的地址endpoint,此处需要通过一个工具函数make_endpoint()来创建。

DumbServer唯一的普通成员函数Start():

cpp 复制代码
void Start()
{
    boost::asio::io_service& ios = 
                _acceptor.get_io_service();
    //存储客户端发来的连接
    auto peer_socket = 
        make_shared<boost::asio::ip::tcp::socket>(ios);
    //存储客户端的端点
    auto peer_endpoint = 
        make_shared<boost::asio::ip::tcp::endpoint>();

Start()方法是要让"_acceptor"执行async_accept(...)方法。

peer_scoket和peer_endpoint都被定义为shared_ptr。它们将存活着,当有新连接产生时,它们一个用来存储网络连接底下的套接字,一个用来存储网络连接对端的地址;直到连接被断开不再需要时,链式传递结束,二者自动释放。

重点来了,看看async_accept()方法如何调用:

cpp 复制代码
    _acceptor.async_accept(*peer_socket, *peer_endpoint
            , [peer_socket, peer_endpoint, this]
                (boost::system::error_code const& err)
    {
        if(err)
        {
            cout << err.message() << endl;
        }

        cout << "客户端为:" 
             <<(*peer_endpoint).address().to_string()
             << " : " << (*peer_endpoint).port() << endl;
        //链式任务反应,确保一直在监听处理
        this->Start();
    });
}//end Start()

async_accept()前两个入参都是普通对象的应用,

第一个入参类型是"ip::tcp::socket&",第二个是"endpoint_type const&",我们刚刚创建shared_ptr智能指针,所以只能乖乖地分别通过'*'取值。

这也暗示了我们,将二者传递给async_accept()函数并不是一次链式传递。

相关推荐
过过过呀Glik29 分钟前
在 Ubuntu 上安装 Muduo 网络库的详细指南
linux·c++·ubuntu·boost·muduo
0zxm1 小时前
06 - Django 视图view
网络·后端·python·django
蜀黍@猿1 小时前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧4091 小时前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
zh路西法1 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
轩辰~2 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
燕雀安知鸿鹄之志哉.2 小时前
攻防世界 web ics-06
网络·经验分享·安全·web安全·网络安全
lxyzcm2 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿2 小时前
C/C++基础错题归纳
c++
雨中rain3 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++