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()函数并不是一次链式传递。