细心的同学该发现,该模块就是之前测试代码中监听套接字负责的模块,我们要将其单独拎出来进行管理!
该模块是对 Socket 模块(实现监听套接字的操作)、Channel 模块(实现套接字**IO**事件管理以及事件触发后的回调处理)的一个整体封装,实现了对一个监听套接字的整体的管理。
具体处理流程如下:
- 创建一个监听套接字。
- 启动读事件监控。
- 等到读事件触发后,就获取新连接。
- 然后调用新连接获取成功后的回调函数进行处理。(需要注意的是,事件回调处理函数的设置是由服务器来指定的,该模块就是负责提供设置回调函数的接口!)

下面给出该模块的主体框架:
using AcceptCallBack = std::function<void(int)>;
class Acceptor
{
private:
Socket _socket; // 监听套接字的描述符
EventLoop* _loop; // 用于对监听套接字进行事件监控
Channel _channel; // 用于对监听套接字进行事件管理
AcceptCallBack _accept_callback; // 获取到新连接之后的回调处理函数
public:
Acceptor();
// 启动监听套接字的可读事件监控
void start_listen();
// 设置监听套接字的可读事件回调处理函数
void set_accept_callback(const AcceptCallBack& cb);
private:
// 监听套接字的可读事件回调处理函数--即获取新连接,调用_accept_callback函数进行新连接处理
void read_handle();
// 包装一下创建套接字的过程
int create_socket(uint16_t port);
};Ⅱ. 具体实现
实现其实并不难,最主要的一个细节就是启动监听套接字的可读事件监控不能放在构造函数中进行!因为此时可能还没设置回调函数就已经有新连接到来,那么这些新连接是得不到处理的,因为回调函数还没设置,就会造成内存泄漏问题,所以我们必须搞一个单独的函数 start_listen() 来启动监听套接字的可读事件监控。
using AcceptCallBack = std::function<void(int)>;
class Acceptor
{
private:
Socket _socket; // 监听套接字的描述符
EventLoop* _loop; // 用于对监听套接字进行事件监控
Channel _channel; // 用于对监听套接字进行事件管理
AcceptCallBack _accept_callback; // 获取到新连接之后的回调处理函数
public:
Acceptor(EventLoop* loop, uint16_t port)
: _socket(create_socket(port))
, _loop(loop)
, _channel(_socket.get_fd(), _loop)
{
// 设置监听套接字的可读事件监控
// 但是注意不能先启动可读事件监控,因为此时外部可能还没设置_accept_callback函数的回调!!!
_channel.set_read_callback(std::bind(&Acceptor::read_handle, this));
}
// 启动监听套接字的可读事件监控
void start_listen()
{
// 启动可读事件监控应该由外部控制顺序,必须在set_accept_callback()调用之后再去启动
// 如果在构造函数中启动的话,此时如果还没设置回调函数就已经有新连接到来
// 那么这些新连接是得不到处理的,因为回调函数还没设置,就会造成内存泄漏问题!
_channel.enable_read();
}
// 设置监听套接字的可读事件回调处理函数
void set_accept_callback(const AcceptCallBack& cb) { _accept_callback = cb; }
private:
// 监听套接字的可读事件回调处理函数--即获取新连接,调用_accept_callback函数进行新连接处理
void read_handle()
{
int newfd = _socket.Accept();
if(newfd < 0)
return;
if(_accept_callback)
_accept_callback(newfd);
}
// 包装一下创建套接字的过程
int create_socket(uint16_t port)
{
bool ret = _socket.create_server(port);
assert(ret == true); // 直接断言,如果创建监听套接字失败了,那么其它的都没得说!
return _socket.get_fd();
}
};Ⅲ. 测试代码
1、服务端代码
有了监听套接字模块之后,我们就不需要自己创建一个 Socket 和 Channel 对象了,而是直接使用 Acceptor 对象即可!
#include "../source/server.hpp"
uint64_t id = 1; // 连接id
std::unordered_map<uint64_t, ConnectionPtr> connections; // 连接管理表
EventLoop loop; // 一个EventLoop对象,后面会用线程池来代替,这里作为全局变量使用即可
void connected_handle(const ConnectionPtr& cptr)
{
// 这里的连接建立处理,我们就简单的打印哪个连接建立即可
DLOG("new connection: %p,the id is:%d", cptr.get(), cptr->get_connection_id());
}
void message_handle(const ConnectionPtr& cptr, Buffer* buf)
{
// 这里的消息事件处理,我们就做简单的打印以及回响即可
DLOG("接收到:%s", buf->start_of_read());
buf->push_reader_back(buf->get_sizeof_read());
std::string str = "lirendada 你好啊!";
cptr->send_data(str.c_str(), str.size());
}
void closed_handle(const ConnectionPtr& cptr)
{
// 就是将连接管理表中的该连接去掉
DLOG("delete connection: %p,the id is:%d", cptr.get(), cptr->get_connection_id());
connections.erase(cptr->get_connection_id());
}
void acceptor_callback(int sockfd)
{
// 用Connection包装该新链接,并且设置回调函数
ConnectionPtr cptr(new Connection(&loop, id, sockfd));
cptr->set_connected_callback(std::bind(connected_handle, std::placeholders::_1));
cptr->set_message_callback(std::bind(message_handle, std::placeholders::_1, std::placeholders::_2));
cptr->set_server_closed_callback(std::bind(closed_handle, std::placeholders::_1)); // 注意这里是服务器模块的关闭回调,也就是去掉与该连接的联系
// 启动非活跃销毁功能,并将连接设置为建立完成状态
cptr->enable_inactive_release(3);
cptr->connecting_to_connceted();
// 最后别忘了添加到服务器的连接管理表中
connections[id++] = cptr;
}
int main()
{
// 创建监听套接字,然后利用bind函数设置获取新连接之后的回调函数,并且启动可读监控
Acceptor acceptor(&loop, 8080);
acceptor.set_accept_callback(std::bind(acceptor_callback, std::placeholders::_1));
acceptor.start_listen();
// 启动事件监控
loop.start();
return 0;
}2、客户端代码
客户端的代码和之前一样不变!
#include "../source/server.hpp"
int main()
{
// 创建客户端套接字
Socket client_sock;
client_sock.create_client(8080, "127.0.0.1");
// 做五次简单的发送和回响,所以会刷新五次连接
for(int i = 0; i < 5; ++i)
{
std::string str = "lirendada";
client_sock.Send(str.c_str(), str.size());
char buf[1024] = { 0 };
client_sock.Recv(buf, sizeof(buf) - 1);
DLOG("%s", buf);
sleep(1);
}
// 进入死循环
while(1) sleep(1);
return 0;
}3、执行结果
