Acceptor-Connector模式一(Acceptor的工作)V2.0

本文详细介绍了ACE框架中的Acceptor-Connector模式,包括其工作原理、相关类的功能及其实现流程。该模式适用于面向连接的TCP/IP协议通信场景,能够高效地处理连接的建立与管理。

 前言:ACE Acceptor-Connector模式

首先这种模式肯定是面向连接的TCP/IP协议。
不管是什么场景,几乎面向连接的通信程序总是由一端主动发起连接,一端监听等待对方的连接。
这就是接收器-连接器模式(服务器-客户端)。

模式思想

(1)此模式只负责连接的建立,不管有多少连接上来,这个模式都能应对。
(2)至于连接建立之后如何通信,那是通信处理器的事情,与此模式不再有任何关系。
(3)资源的管理总是通过调用函数的返回值来做约定的处理。不用类型如果有特殊的资源管理需求,均可以覆盖父类的方法。

相关类

(1)ACE_Svc_Handler  : ACE_Task (服务处理器)连接服务的本地端,成员变量PEER_STREAM peer_由模板参数化,一般为ACE_SOCK_Stream,负责通信的具体执行。ACE_SOCK_Stream既可以读也可以写socket。
(2)ACE_Acceptor (接受器)该工厂被动的接受连接,并随即初始化一个ACE_Svc_Handler 对象来响应。接收器的handle_input方法截获连接信息,并创建服务处理类,最后执行服务处理类的open方法启动服务处理流程。
(3)ACE_Connector (连接器)该工厂主动的连接到对端接受器,并随即初始化一个ACE_Svc_Handler 对象来响应
(4)ACE_Reactor 执行不依赖于应用的一般的消息分发策略

一、接收器的工作

接收器的工作流程:
(1)接收器的open函数会在指定端口监听
(2)将自己与AACE_Event_Handler::ACCEPT_MASK事件一起注册到反应器
(3)对端发起连接请求之后,反应器回调ACE_Acceptor ::handle_input方法
(4)handle_input方法就会创建连接处理器对象,并调用连接处理器的open方法
(5)handle_input方法返回-1的时候反应器就会回调接收器的handle_close方法,(参考 ACE_Reactor的工作逻辑
(6)接收器的handle_close方法主要工作是关闭注册事件,并关闭socket资源(执行peer_acceptor_.close())

二、接收器handle_input的工作

handle_input的工作主要包括三部分:
(1)根据模板参数创建服务处理对象new ACE_Svc_Handler
(2)调用成员ACE_SOCK_Acceptor peer_acceptor_的acceptor方法,指定当连接成功建立之后由服务处理对象ACE_Svc_Handler来处理连接
(3)调用服务处理对象的open方法
1)服务处理对象的open方法会注册ACE_Event_Handler::READ_MASK事件到ACE_Reactor
2)服务处理对象的open方法会发起receive动作开始读,
3)服务处理对象的读取完成会执行handle_input,至于服务处理对象的handle_input方法返回-1则是 ACE_Reactor的处理逻辑了。
(4)服务处理对象的open方法如果返回-1,则调用服务处理对象的close方法(),ACE_Svc_Handler的close方法会调用handle_close,进而调用destroy,进而调用delete this
template <typename PEER_STREAM, typename SYNCH_TRAITS> int
ACE_Svc_Handler<PEER_STREAM, SYNCH_TRAITS>::handle_close (ACE_HANDLE,
                                                       ACE_Reactor_Mask)
{
  ACE_TRACE ("ACE_Svc_Handler<PEER_STREAM, SYNCH_TRAITS>::handle_close");

  if (this->reference_counting_policy ().value () ==
      ACE_Event_Handler::Reference_Counting_Policy::DISABLED)
    {
      this->destroy ();
    }

  return 0;
}
template <typename PEER_STREAM, typename SYNCH_TRAITS> void
ACE_Svc_Handler<PEER_STREAM, SYNCH_TRAITS>::destroy (void)
{
  ACE_TRACE ("ACE_Svc_Handler<PEER_STREAM, SYNCH_TRAITS>::destroy");

  // Only delete ourselves if we're not owned by a module and have
  // been allocated dynamically.
  if (this->mod_ == 0 && this->dynamic_ && this->closing_ == false)
    // Will call the destructor, which automatically calls <shutdown>.
    // Note that if we are *not* allocated dynamically then the
    // destructor will call <shutdown> automatically when it gets run
    // during cleanup.
    delete this;
}

示例

下面的程序会在1500端口持续监听,每当有对端发来连接,就创建本地连接处理对象,创建完之后就通过连接处理对象的open返回-1来告诉反应器来释放该对象。

Acceptor 服务端

#include <ace/Log_Msg.h>
#include "ace/Svc_Handler.h"
#include <ace/Acceptor.h>
#include "ace/SOCK_Acceptor.h"
#include "ace/INET_Addr.h"
#include "ace/Reactor.h"

class My_Time_Server_Handler : public ACE_Svc_Handler<ACE_SOCK_Stream,ACE_NULL_SYNCH>
{
public:
	~My_Time_Server_Handler();
	//由接收器在接收到对端的连接请求之后调用此方法
	virtual int open(void *)
	{
		ACE_DEBUG((LM_DEBUG,"one client connection established.\n"));
		return -1;// ERROR
	}
};

My_Time_Server_Handler::~My_Time_Server_Handler()
{
	ACE_DEBUG((LM_DEBUG,"~My_Time_Server_Handler()\n"));
}

int main(int argc, char *argv[])
{
	ACE_INET_Addr local_addr(1500);

	ACE_Acceptor<My_Time_Server_Handler,ACE_SOCK_Acceptor> acceptor;

	acceptor.open(local_addr,ACE_Reactor::instance());
	//截过连接信息
	ACE_Reactor::instance()->run_reactor_event_loop();

	return 0;
}
Connector客户端

#include <ace/Log_Msg.h>
#include <ace/SOCK_Connector.h>
#include "ace/INET_Addr.h"

int main(int argc, char *argv[])
{
	ACE_INET_Addr addr(1500,"127.0.0.1"); //remote address
	ACE_SOCK_Connector client_connetor; // connetor for socket client
	ACE_SOCK_Stream sock_stream; // stream is for socket read/write

	if(client_connetor.connect(sock_stream,addr)==-1) //connect to remote address
	{
		ACE_DEBUG ((LM_DEBUG,ACE_TEXT ("(%P|%t) %p\n"),ACE_TEXT ("connection failed!")));
		return -1;
	}
	if (sock_stream.close () == -1) //close the connection
	{
		ACE_ERROR ((LM_ERROR,ACE_TEXT ("(%P|%t) %p\n"),ACE_TEXT ("sock close")));
		return -1;
	}
	return 0;
}

运行结果:每次执行客户端都会创建一个服务端的服务处理对象,并随即被释放(我们故意让open方法返回-1来释放服务处理对象,从而来说明整个事件的流程)





<think>问题:运行acceptor_->listen()后是否就是创建了服务器端口? 回答: 运行`acceptor_->listen()`后,套接字(socket)会被绑定到指定的端口并开始监听连接请求,但在此之前必须已经通过`bind`操作将套接字与个本地端点(IP地址和端口号)关联。在Boost.Asio中,通常在创建`acceptor`对象时就会指定端点(即绑定端口),然后调用`listen()`使其开始监听。 具体步骤: 1. 创建`acceptor`对象,并传入`io_context`和端点(endpoint)。这步会打开套接字并绑定到指定端口。 2. 调用`listen()`方法,使套接字进入监听状态,准备接受传入的连接。 因此,`listen()`调用是使服务器端口开始监听的关键步骤,但端口的绑定发生在创建`acceptor`时(或显式调用`bind`)。 代码示例: ```cpp #include <boost/asio.hpp> #include <iostream> using namespace boost::asio; using ip::tcp; int main() { io_context ioc; // 创建端点:任意地址,端口8080 tcp::endpoint endpoint(tcp::v4(), 8080); // 创建acceptor并打开(此时尚未绑定) tcp::acceptor acceptor(ioc); // 打开acceptor(相当于创建套接字) acceptor.open(endpoint.protocol()); // 绑定到端点(此时端口被占用) acceptor.bind(endpoint); // 开始监听(设置等待队列长度) acceptor.listen(); std::cout << "Server is listening on port 8080" << std::endl; // 通常在这里开始异步接受连接 // ... ioc.run(); return 0; } ``` 或者更简洁的方式(在构造函数中步完成打开和绑定): ```cpp tcp::acceptor acceptor(ioc, endpoint); // 创建时即绑定 acceptor.listen(); // 然后开始监听 ``` 解释: - `acceptor.open()`:打开套接字,但尚未绑定端口。 - `acceptor.bind()`:将套接字绑定到指定端点(IP和端口),此时端口被占用。 - `acceptor.listen()`:使套接字进入监听状态,可以接受传入的连接。同时可以设置等待连接队列的最大长度(通过参数,默认由系统决定)。 注意:在Boost.Asio中,如果使用`tcp::acceptor acceptor(ioc, endpoint);`这样的构造函数,它会自动完成打开和绑定,然后只需要调用`listen()`即可。 因此,运行`listen()`之后,服务器端口才真正开始监听连接请求。但端口的创建和绑定发生在之前(构造时或显式调用`bind`时)。 §§相关问题§§: 1. Boost.Asio中,`acceptor`的构造函数中传入端点(endpoint)和后续调用`bind`有什么区别? 2. 如果端口已被占用,调用`bind`时会抛出什么异常? 3. 在调用`listen()`之前,如果尝试接受连接会发生什么? 4. 如何设置`listen()`的等待队列长度?队列满时会发生什么?</think>### 运行 `acceptor_->listen()` 后是否创建了服务器端口? **答案:不完全正确。** `acceptor_->listen()` 的作用是**启动监听**,但端口的创建和绑定发生在更早阶段。完整的端口创建流程如下: #### 端口创建流程(按顺序): 1. **创建套接字** (`open`) - 创建底层TCP套接字文件描述符 - 尚未绑定任何端口 2. **绑定端口** (`bind`) - 将套接字与特定IP地址和端口号关联 - **此时端口才真正被创建和占用** 3. **启动监听** (`listen`) - 设置套接字为被动监听模式 - 建立连接请求队列 - 允许客户端开始连接 #### 关键代码示例: ```cpp #include <boost/asio.hpp> #include <iostream> int main() { boost::asio::io_context ioc; // 步骤1: 创建acceptor(隐式open) boost::asio::ip::tcp::acceptor acceptor(ioc); // 步骤2: 绑定端口(实际创建端口) boost::asio::ip::tcp::endpoint endpoint( boost::asio::ip::tcp::v4(), // IPv4地址 8080 // 端口号 ); acceptor.bind(endpoint); // 此时端口被创建并占用 // 步骤3: 启动监听 acceptor.listen(); // 设置监听队列 std::cout << "Server listening on port " << acceptor.local_endpoint().port() << std::endl; ioc.run(); return 0; } ``` #### 验证方法: 1. **绑定后立即检查端口占用**: ```cpp // 绑定后检查端口 std::cout << "Bound port: " << acceptor.local_endpoint().port() << std::endl; // 系统命令验证 (Linux/MacOS) system("netstat -an | grep 8080"); ``` 2. **错误处理场景**: ```cpp try { acceptor.bind(endpoint); } catch (const boost::system::system_error& e) { // 端口已被占用时将抛出异常 std::cerr << "Bind failed: " << e.what() << std::endl; } ``` #### 关键区别: | 操作 | 作用 | 是否创建端口 | |--------------|-------------------------------|--------------| | `open()` | 创建套接字文件描述符 | ❌ | | `bind()` | 关联IP和端口(实际创建端口) | ✔️ | | `listen()` | 启用连接监听队列 | ❌ | #### 完整服务器启动流程: ```cpp void start_server() { // 1. 创建acceptor(隐式open) acceptor_.reset(new tcp::acceptor(*ioc_)); // 2. 绑定端口(实际创建端口) tcp::endpoint ep(tcp::v4(), port_); acceptor_->open(ep.protocol()); // 显式打开 acceptor_->set_option(tcp::acceptor::reuse_address(true)); acceptor_->bind(ep); // 端口创建点 // 3. 启动监听 acceptor_->listen(); // 设置默认队列长度 // 4. 开始接受连接 async_accept_next(); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C++程序员Carea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值