回顾

在上一篇文章中我们我们实现了Reactor的封装部分,把socket,listen,bind,IO等系统调用,使用C++的类进行类封装,我们要明确一点为什么要使用C++进行封装,C语言也能完成这些工作。因为使用C++类进行封装会有更好的可拓展性,方便后续的功能添加和版本迭代。
Reactor_v2版本的实现
我们已经完成了封装的部分,下面要是实现我们的核心功能,就是事件驱动。也就是对epoll相关进行封装。
本质上就是将epoll的三个函数(epoll_create、epoll_ctl、epoll_wait)以及对应的两个文件描述符的可读事件的处理(Socket类创建的listenfd、以及Acceptor中的accept函数的返回的标识连接创建的文件描述符connfd)
对应的数据成员设计如下:
epoll_create函数的返回结果,需要将其设置为数据成员,因为该文件描述符需要被epoll的另外两个函数进行使用,所以要设置为数据成员
int _epfd;epoll_wait函数需要将就绪的文件描述符放在struct epoll_event结构体,也就是填充epoll_wait的第二个参数,后续还需要使用该结构体,也需要将其设置为数据成员,
vector<struct epoll_event> _evtList;如果监听的文件描述符listenfd可读,就说明有新的连接,就需要调用accept函数进行接受,但是该函数被封装到Acceptor类中了,所以需要使用Acceptor类创建的对象,才能获取到accept函数,可以使用Acceptor的对象(对应的引用也可以、对象的指针也可以,目的都是为了获取其中的成员函数accept),这里可以选择Acceptor类型的引用,
Acceptor & _acceptor;对于每一个accept执行结束后,说明连接已经建立,所以需要有一个对应的连接对象,也就TcpConnection,但是每个对象会关联一个文件描述符connfd,所以有一个记录文件描述符到TcpConnection的数据结构,可以使用键值对进行存储,这样以后就可以通过文件描述符就可以找到对应的连接,同时文件描述符是不重复的,所以这里可以选择的数据结构有map或者unordered_map,我们这里选择map,即
map<int, TcpConnection>,但是注意,TcpConnection是不能进行复制或者赋值的,并且创建出来之后还需要在后续使用该连接,所以最好可以设置为堆对象,否则有可能提前销毁了,所以为了更好的管理,可以使用智能指针shared_ptr管理,也就是map<int, shared_ptr<TcpConnection>> _conns;为了标识整个事件循环是否运行,可以设置标志位
bool _isLooping
成员函数的设计如下:
createEpollFd函数:作用就是为了封装epoll的epoll_create函数,创建文件描述符;
addEpollReadFd函数:作用就是为了封装epoll的epoll_ctl函数,监听对应的文件描述符,例如:listenfd与connfd(这两个文件描述符上述都有描述);
delEpollReadFd函数:作用也是为了封装epoll的epoll_ctl函数,不过是删除对文件描述符的监听。
构造函数:作用就是为了初始化数据成员,这里只有需要对引用数据成员需要额外进行初始化,所以使用EventLoop(Acceptor &acceptor);
循环函数loop:对于IO多路复用epoll而言,核心就是大循环,里面封装epoll_wait函数;
非循环函数loop:这里可以让其不循环,直接跳出;
waitEpollFd函数:里面封装epoll_wait函数,然后就是两大业务逻辑,文件描述符listenfd对应可读的新连接的建立、以及connfd文件描述符可读的读写操作;
handleNewConnection函数:处理文件描述listenfd的可读事件,调用Acceptor中的accept函数获得文件描述符connfd以及使用文件描述符connfd创建连接TcpConnection的对象,然后监听文件描述符connfd,创建文件描述符connfd与连接TcpConnection的对象键值对,便于通过文件描述符可以找到对应的连接;
handleMessage函数:通过文件描述符,映射出对应的连接,然后处理读写事件。
这就是对于epoll逻辑的分析而设计出来的数据成员与成员函数
类图设计

需要通过epoll_create函数进行创建的文件描述符_epfd,并且该文件描述符需要跨函数进行使用,所以将其设置为了数据成员。
为了将就绪的文件描述符存起来,所以设置了vector<struct epoll_event> _evtList数据结构;
为了能标识整个代码是否在循环,设置了标志位_isLooping;
因为在listenfd就绪的时候,需要处理新的连接请求,所以需要调用Acceptor类中的accept函数,所以将Acceptor类型的对象的引用作为数据成员;
只要Acceptor类的对象调用accept有正确的返回结果,就表明三次握手建立成功了,就可以创建TcpConnection对象,为了更好的管理TcpConnection对象,可以将其用map存起来,并且为了防止复制或者赋值,可以使用shared_ptr将其存起来 map<int, shared_ptr<TcpConnection>>,对于智能指针可以用using起别名为TcpConnectionPtr
TCP网络编程过程中的三个半事件
再回顾一下对socket网络编程的认识,里面使用了TCP协议、TCP的三次握手建立连接、TCP的四次挥手断开连接、以及在连接建立好后与客户端进行数据的读写操作,这里面实际上包含着一些事件,也就是:
对于服务端而言
连接建立:包括服务器端被动接受连接(accept)和客户端主动发起连接(connect)。TCP连接一旦建立,客户端和服务端就是平等的,可以各自收发数据。
连接断开:包括主动断开(close、shutdown)和被动断开(read()返回0)。
消息到达:文件描述符可读(如果是listenfd可读表示新的连接请求,如果是connectfd可读表示客户端发数据了),这是最为重要的一个事件,对它的处理方式决定了网络编程的风格(阻塞还是非阻塞,如何处理分包,应用层的缓冲如何设计等等)。
消息发送完毕:这算半个。对于低流量的服务,可不必关心这个事件;这里的"发送完毕"是指应用程序将数据写入操作系统缓冲区(内核缓冲区),不代表对方已经接收到数据。因为后续将由TCP协议栈负责数据的发送与重传,这部分不属于应用程序能掌控的范围。例如在网络延迟、丢包等情况下,即使应用层显示发送完毕,数据也可能没到达接收方,所以从完整通信链条看,它只完成了前半部分,故称 "半个事件" 。
可以在测试代码中加上这三个事件的函数,都跟连接相关,那就需要通过传参提供TcpConnection
在连接建立、消息到达、连接断开时,分别做些什么事情
添加三个事件的回调(类图)
注意
- 三个回调都是与连接相关的,所以应该直接注册给TcpConnection,但是TcpConnection的对象是在EventLoop中创建的,所以需要先将三个回调交给EventLoop,然后传递给TcpConnection对象。
- 三个回调用的形式其实就是function对象,但是为了表示都与TcpConnection相关,所以不能设置为
function<void()>,而是需要将TcpConnection对象带上,才能表示与连接相关,所以可以设置为function<void(const TcpConnectionPtr &)>
添加回调的类图
用作回调的这三个function对象作为EventLoop类的数据成员,可以交给本类的成员函数handleNewConnection,因为在这个成员函数中创建了TcpConnection对象。
TcpConnection中添加与EventLoop新增的一样的三个数据成员与三个成员函数,因为本质上EventLoop仅仅只是作为中转。通过三个setXXX系列的成员函数接收了三个回调(测试文件中提供给EventLoop,EventLoop提供给TcpConnection)后,还需要添加执行相应回调的函数,再加上三个handleXXX系列的成员函数
接下来又要考虑一个新的问题
对于TcpConnection类的setXXX函数而言(以setNewConnectionCallback函数为例),如果说像EventLoop类的setNewConnectionCallback函数一样使用右值引用作为函数形参,那么在EventLoop的handleNewConnection函数中调用TcpConnection的setNewConnectionCallback函数时,传入的是EventLoop的数据成员_onNewConnection,需要用到std::move,而TcpConnection的数据成员_onNewConnection接收后实际起到了接管底层函数的效果(移动赋值函数),相当于EventLoop的数据成员_onNewConnection成了空的function,可能会影响后面的连接。所以可以在TcpConnection类的setNewConnectionCallback函数的形参位置写上const左值引用的形式,既可以接左值,也可以接右值,并且用这个const引用给TcpConnection类的_onNewConnection赋值时就会调用function的赋值运算符函数(起到复制效果,而不是移交管理权效果)
当然,如果不考虑function对象复制的开销,在所有的函数参数中都直接使用function对象也是可以的。
EventLoop.h
cpp
#ifndef __EVENTLOOP_H
#define __EVENTLOOP_H
#include<sys/epoll.h>
#include"Acceptor.h"
#include"TcpConnection.h"
#include<vector>
#include<map>
#include<memory>
#include<functional>
using std::vector;
using std::map;
using std::shared_ptr;
using std::function;
using TcpConnectionPtr = shared_ptr<TcpConnection>;
using TcpConnectionCallback = function<void(const TcpConnectionPtr &)>;
class EventLoop{
public:
EventLoop(Acceptor & acc);
~EventLoop();
void loop();
void unloop();
// 注册回调
void setNewConnectionCallback(TcpConnectionCallback && cb);
void setMessageCallback(TcpConnectionCallback && cb);
void setCloseCallback(TcpConnectionCallback && cb);
private:
void waitEpollfd();
void handleNewConnection();
void handleMessage(int fd);
int createEpollFd();
void addEpollReadFd(int fd);
void delEpollReadFd(int fd);
private:
int _epfd; // epoll_create创建的文件描述符
vector<struct epoll_event> _evtList; // 存放就绪文件描述符的容器
bool _isLooping; // 是否在循环的标志
Acceptor & _acceptor; // Acceptor 引用
map<int, shared_ptr<TcpConnection>> _conns; // 存放描述符与TcpConnection连接的键值对
// 三个回调函数
TcpConnectionCallback _onNewConnection;
TcpConnectionCallback _onMessage;
TcpConnectionCallback _onClose;
};
#endif
EventLoop.cpp
cpp
/* ************************************************************************
> File Name: EventLoop.cpp
> Author: hyj
> mail: 1683958261@qq.com
> Created Time: Mon 03 Aug 2026 09:45:24 AM CST
> Description:
************************************************************************/
#include"EventLoop.h"
#include"logger.h"
#include<iostream>
#include<string.h>
#define LISTEN_CLIENT_MAX_NUM 1024
EventLoop::EventLoop(Acceptor & acc):
_epfd(createEpollFd()),
_evtList(LISTEN_CLIENT_MAX_NUM),
_isLooping(false),
_acceptor(acc),
_conns(){
// 把listenfd放到红黑树上
int listenfd = _acceptor.fd();
addEpollReadFd(listenfd);
}
EventLoop::~EventLoop(){
::close(_epfd);
}
void EventLoop::loop(){
_isLooping = true;
while(_isLooping){
waitEpollfd();
}
}
void EventLoop::unloop(){
_isLooping = false;
}
void EventLoop::waitEpollfd(){
int nready = 0;
nready = epoll_wait(_epfd, _evtList.data(), _evtList.capacity(),3000);
if(nready == -1){
INFO_LOG("epoll_wait error");
return ;
}
else if(nready == 0){
INFO_LOG("epoll_wait timeout");
}
else{
// 处理文件描述符个数到达上限,触发扩容
if(nready == (int)_evtList.capacity()){
_evtList.resize(2 * nready);
}
for(int idx = 0;idx < nready; ++idx){
int listenfd = _acceptor.fd();
int fd = _evtList[idx].data.fd;
// 如果监听到listenfd则说明有有新的连接
if(fd == listenfd){
handleNewConnection();
}
// 如果是其它文件描述符,则说明有客户端发来消息
else{
handleMessage(fd);
}
}
}
}
void EventLoop::handleNewConnection(){
// 获取新的连接
int connfd = _acceptor.accept();
if(connfd < 0){
ERROR_LOG("accept failed!");
return ;
}
// 放到红黑树上
addEpollReadFd(connfd);
// 智能指针管理新的连接
TcpConnectionPtr con(new TcpConnection(connfd));
con->setNewConnectionCallback(_onNewConnection);
con->setMessageCallback(_onMessage);
con->setCloseCallback(_onClose);
/* 使用回调
// 打印连接日志
cout << con->toString() << "has connected" << endl;
_onNewConnection(con)*/
con->handleNewConnectionCallback();
//把新的连接放到map中进行管理 _conn[connfd] = con;
_conns.insert({connfd, con});
}
void EventLoop::handleMessage(int fd){
auto it = _conns.find(fd);
if(it != _conns.end()){
bool isclose = it->second->isClosed();
if(isclose){
/*换成回调*/
//cout << it->second->toString() << "has closed" << endl;
it->second->handleCloseCallback();
delEpollReadFd(fd);
_conns.erase(it);
}
else{
/*换成回调
// 接收客户端的数据
string msg = it->second->receive();
cout << ">> recv msg from client:" << msg << endl;
*/
it->second->handleMessageCallback();
// 服务器处理数据
//
//
//
// 发回给客户端
//it->second->send(msg);
}
}
else{
INFO_LOG("connection dispear");
return ;
}
}
int EventLoop::createEpollFd(){
int epfd = epoll_create(LISTEN_CLIENT_MAX_NUM);
if(epfd == -1){
ERROR_LOG("Epoll Create Error");
exit(EXIT_FAILURE);
}
return epfd;
}
void EventLoop::addEpollReadFd(int fd){
struct epoll_event evt;
memset(&evt, 0, sizeof(evt));
evt.events = EPOLLIN;
evt.data.fd = fd;
if(epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &evt) == -1){
ERROR_LOG("epoll_ctl add error");
exit(EXIT_FAILURE);
}
}
void EventLoop::delEpollReadFd(int fd){
if(epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, NULL) == -1){
ERROR_LOG("epoll_ctl del error");
exit(EXIT_FAILURE);
}
}
void EventLoop::setNewConnectionCallback(TcpConnectionCallback && cb){
_onNewConnection = cb;
}
void EventLoop::setMessageCallback(TcpConnectionCallback && cb){
_onMessage = cb;
}
void EventLoop::setCloseCallback(TcpConnectionCallback && cb){
_onClose = cb;
}
TcpConnection.h
cpp
/**
* 连接管理
*/
#ifndef __TCPCONNECTION_H
#define __TCPCONNECTION_H
#include<string>
#include"Socket.h"
#include"SocketIO.h"
#include"InetAddress.h"
#include<functional>
#include<memory>
using std::string;
using std::function;
using std::shared_ptr;
class TcpConnection;
using TcpConnectionPtr = shared_ptr<TcpConnection>;
using TcpConnectionCallback = function<void(const TcpConnectionPtr &)>;
class TcpConnection
:public std::enable_shared_from_this <TcpConnection>{
public:
/**
* @param fd
*/
explicit TcpConnection(int fd);
~TcpConnection();
string receive();
string toString();
bool isClosed();
void setNewConnectionCallback(const TcpConnectionCallback & cb);
void setMessageCallback(const TcpConnectionCallback & cb);
void setCloseCallback(const TcpConnectionCallback & cb);
void handleNewConnectionCallback();
void handleMessageCallback();
void handleCloseCallback();
void send(const string & msg);
private:
InetAddress getLocalAddr();
InetAddress getPeerAddr();
/**
* @param msg
*/
private:
SocketIO _sockIO;
// 辅助打印连接日志信息
Socket _sock;
InetAddress _localAddress;
InetAddress _peerAddr;
// 回调
TcpConnectionCallback _onNewConnection;
TcpConnectionCallback _onMessage;
TcpConnectionCallback _onClose;
};
#endif //_TCPCONNECTION_H
TcpConnection.cpp
cpp
#include "TcpConnection.h"
#include<sstream>
#include"logger.h"
using std::ostringstream;
/**
* TcpConnection implementation
*/
/**
* @param fd
*/
TcpConnection::TcpConnection(int fd)
:_sockIO(fd),
_sock(fd),
_localAddress(getLocalAddr()),
_peerAddr(getPeerAddr()){
}
TcpConnection::~TcpConnection() {
}
/**
* @return string
*/
string TcpConnection::receive() {
char buffer[1024] = {0};
_sockIO.readLine(buffer, sizeof(buffer));
return string(buffer);
}
/**
* @param msg
* @return void
*/
void TcpConnection::send(const string & msg) {
_sockIO.writen(msg.c_str(), msg.size());
return;
}
string TcpConnection::toString(){
ostringstream oss;
oss << _localAddress.getIp() << ":"
<< _localAddress.getPort() << "------------>"
<< _peerAddr.getIp() << ":"
<< _peerAddr.getPort() ;
return oss.str();
}
InetAddress TcpConnection::getLocalAddr(){
struct sockaddr_in addr;
socklen_t len = sizeof(struct sockaddr_in);
if(::getsockname(_sock.getFd(),(struct sockaddr*)&addr, &len)){
WARN_LOG("getLocalAddr");
}
return InetAddress(addr);
}
InetAddress TcpConnection::getPeerAddr(){
struct sockaddr_in addr;
socklen_t len = sizeof(struct sockaddr_in);
if(::getpeername(_sock.getFd(),(struct sockaddr*)&addr, &len)){
WARN_LOG("getPeerAddr");
}
return InetAddress(addr);
}
bool TcpConnection::isClosed(){
// 进行一次拷贝式的预读
char buff[5] = {0};
int ret = recv(_sock.getFd(), buff, sizeof(buff), MSG_PEEK);
return 0 == ret;
}
void TcpConnection::setNewConnectionCallback(const TcpConnectionCallback & cb){
_onNewConnection = cb;
}
void TcpConnection::setMessageCallback(const TcpConnectionCallback & cb){
_onMessage = cb;
}
void TcpConnection::setCloseCallback(const TcpConnectionCallback & cb){
_onClose = cb;
}
void TcpConnection::handleNewConnectionCallback(){
if(_onNewConnection){
_onNewConnection(shared_from_this());
}
else{
INFO_LOG("_onNewConnection is empty");
}
}
void TcpConnection::handleMessageCallback(){
if(_onMessage){
_onMessage(shared_from_this());
}
else{
INFO_LOG("_onMessage is empty");
}
}
void TcpConnection::handleCloseCallback(){
if(_onClose){
_onClose(shared_from_this());
}
else{
INFO_LOG("_onClose is empty");
}
}
main.cpp
cpp
#include "Acceptor.h"
#include "TcpConnection.h"
#include <iostream>
#include "EventLoop.h"
using std::cout;
using std::endl;
void onNewConnection(const TcpConnectionPtr & con){
cout << con->toString() << " connected!" << endl;
}
void onMessage(const TcpConnectionPtr & con){
string msg = con->receive();
cout << "recv msg from client:" << msg <<endl;
msg = "msg:"+msg;
con->send(msg);
}
void onClose(const TcpConnectionPtr & con){
cout << con->toString() << " closed " << endl;
}
void test0()
{
Acceptor acceptor("0.0.0.0",8080);
acceptor.ready();
EventLoop eloop(acceptor);
eloop.setNewConnectionCallback(onNewConnection);
eloop.setMessageCallback(onMessage);
eloop.setCloseCallback(onClose);
eloop.loop();
}
int main()
{
test0();
return 0;
}
运行结果

完成到这里其实我们已经完成了核心的功能框架,为了更好的使用,可以对当前进行一次封装,但这是次要问题。封装之后我们会发现一个问题,我们的服务器响应是串行执行的,这就极大的降低了我们服务器的性能。所以在下一步最主要的问题应该是解决并发响应的问题

