1.使用ET+非阻塞设计一个正确的IO服务器
要注重模块化解耦
1.1 TcpServer的大致框架
bash
#include<iostream>
#include<memory>
#include<unordered_map>
#include"Listener.hpp"
#include"Epoller.hpp"
#include"Connection.hpp"
class TcpServer{
public:
TcpServer()
:_epoll_ptr(std::make_unique<Epoller>()){}
void Start(){
while(true)
{
_epoll_ptr->Wait();
}
}
private:
//1. epoll模型
std::unique_ptr<Epoller> _epoll_ptr;
//2. listen模块 监听连接
std::shared_ptr<Listener> _listener_ptr;
//3.管理所有的Connection ,本质是管理未来获取的fd
std::unordered_map<int,std::shared_ptr<Connection>> _Connections;
};
注意,之前关于epoll的使用,是直接默认为不会阻塞,一次全部读完的,但此处不可了

原因
1 . LT buffer局内变量,下次读,buffer清空
- buffer也会被多个连接读取
因此需要创建一个模块来进行单个fd的读与写缓冲区,但又因为会有大量的Connection,需要进行管理,所以使用unordered_map
Connection.hpp
bash
#include<iostream>
#include"InetAddr.hpp"
class Tcpserver;
class Connection{
public:
Connection(){}
~Connection(){}
private:
int _fd;
std::string _inbuffer;
std::string _outbuffer;
//回调指针
Tcpserver*owner;
InetAddr* client_add;
};
1.2 Listener继承Connection
Connection类应该是一个 "基础连接类"(比如已经封装了 "网络连接的基本操作",比如创建 socket、收发数据等)。
而Listener是 "专门负责监听新连接" 的模块,它需要用到 Connection 里的基础连接能力 (比如创建监听用的 socket),同时又要扩展 "监听端口、接受新连接" 的功能 ------ 所以用继承来复用 Connection 的代码,避免重复写基础连接的逻辑。
Connection
bash
#include<iostream>
#include"InetAddr.hpp"
class Tcpserver;
class Connection{
public:
Connection(){}
virtual void Recver()=0;
virtual void Sender()=0;
virtual void Excepter()=0;
~Connection(){}
private:
int _fd;
std::string _inbuffer;
std::string _outbuffer;
//回调指针
Tcpserver*owner;
InetAddr* client_add;
};
Listener.hpp
bash
#include<iostream>
#include "Socket.hpp"
#include "Connection.hpp"
#include"Common.hpp"
using namespace SocketModule;
//连接和监听 两个逻辑上差不多,进行多态,减少重复代码
class Listener :public Connection
{
public:
Listener(int port=defaultport)
:_port(port){
_listensock->BuildTcpSocketMethod(_port);
}
void Recver(){
}
void Sender(){}
void Excepter(){}
~Listener(){}
private:
int _port;
std::unique_ptr<Socket> _listensock;
};
1.3 初步完善 搭建大致框架
epoller.hpp
bash
#pragma once
#include <sys/epoll.h> // 必须包含,epoll_* 函数声明
#include <unistd.h> // close 函数
#include <cstdlib> // exit 函数
#include <iostream> // 日志相关(若LogModule基于此)
#include "Log.hpp" // 确保Log.hpp正确声明LogModule命名空间和LOG宏
#include "Common.hpp"
using namespace LogModule;
class Epoller
{
public:
Epoller()
: _epfd(-1)
{
_epfd = epoll_create(126);
if (_epfd < 0)
{
LOG(LogLevel::ERROR) << "epoll_create fail";
exit(EPOLL_CREATE_ERROR);
}
LOG(LogLevel::INFO) << "epoll_create success , _epfd is :" << _epfd;
}
void AddEvent(int fd, uint32_t event)
{
struct epoll_event ev;
ev.data.fd = fd;
ev.events = event;
int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
if (n < 0)
{
LOG(LogLevel::ERROR) << "epoll_ctl error";
return;
}
LOG(LogLevel::INFO) << "epoll_ctl success: " << fd;
}
void DelEvent() {}
void ModEvent() {}
void Wait() {}
~Epoller() {
if(_epfd>0)
{
close(_epfd);
_epfd = -1; // 置为非法值,防止后续误操作
}
}
private:
int _epfd;
};
connection.hpp
bash
#pragma once
#include<iostream>
#include"InetAddr.hpp"
class Tcpserver;
class Connection{
public:
Connection(){}
virtual void Recver()=0;
virtual void Sender()=0;
virtual void Excepter()=0;
int GetFd(){return _fd;}
void SetFd(int fd)
{
_fd=fd;
}
uint32_t GetEvent(){return _event;}
void SetEvent(const uint32_t event)
{
_event=event;
}
~Connection(){}
private:
int _fd;
std::string _inbuffer;
std::string _outbuffer;
//回调指针
Tcpserver*owner;
InetAddr* client_add;
//关心事件
uint32_t _event;
};
listener.hpp
bash
#pragma once
#include <iostream>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "Connection.hpp"
#include "Common.hpp"
using namespace SocketModule;
// 连接和监听 两个逻辑上差不多,进行多态,减少重复代码
class Listener : public Connection
{
public:
Listener(int port = defaultport)
: _port(port), _listensock(std::make_unique<TcpSocket>())
{
// 将监视的添加到
_listensock->BuildTcpSocketMethod(_port);
SetEvent(EPOLLIN); // ET todo
SetFd(_listensock->Fd());
}
void Recver()
{
}
void Sender() {}
void Excepter() {}
~Listener() {}
private:
int _port;
std::unique_ptr<Socket> _listensock;
};
1.4 完成等待事件就绪的封装
bash
int Wait(struct epoll_event*events,int maxevents,int timeout)
{
int n=epoll_wait(_epfd,events,maxevents,timeout);
if(n<0)
LOG(LogLevel::ERROR)<<"epoll_wait faial";
return n;
}
1.5 完成等待就绪后的事件操作
这里我们不再只关注写与读,而是其他的也要关注
但我们通过将其他错误转化为IO错话,然后再转化为一个函数解决,减少我们的麻烦

bash
void Start(){
if(IsConnectionEmpty())
return;
_isrunning=true;
while(_isrunning)
{
int timeout=-1;
int n= _epoll_ptr->Wait(_revs,rev_num,timeout);
//循环直接排除了没有的条件
for(int i=0;i<n;++i)
{
int sockfd=_revs[i].data.fd;//前面刚连接的时候,已经设置进入内核
uint32_t revents=_revs[i].events;
// 1. 将所有的异常处理,统一转化成IO错误 2. 所有的IO异常,统一转换成为一个异常处理函数
if(revents &EPOLLERR)
revents|=(EPOLLIN|EPOLLOUT);// 1. 将所有的异常处理,统一转化成IO错误
if(revents& EPOLLHUP)
revents|=(EPOLLIN|EPOLLOUT);// 1. 将所有的异常处理,统一转化成IO错误
if(revents&EPOLLIN)
{
if(IsConnectionExits(sockfd));
//不用再区分是listen监视 还是普通socket ,因为使用了继承 多态,虽然调用同一个函数 ,但会进行重写
_connections[sockfd]->Recver();
}
if(revents&EPOLLOUT)
{
if(IsConnectionExits(sockfd)); //判断是真的就绪了,还是因为出错导致设置的
_connections[sockfd]->Sender();
}
}
}
}
1.6 对start进一步解耦
bash
bool IsConnectionExits(int sockfd)
{
auto pos = _connections.find(sockfd);
if (pos == _connections.end())
return false;
return true;
}
bool IsConnectionEmpty()
{
return _connections.empty();
}
int LoopOnce()
{
int timeout = -1;
return _epoll_ptr->Wait(_revs, rev_num, timeout);
}
// 事件派发器
void Distribute(int n)
{
// 循环直接排除了没有的条件
for (int i = 0; i < n; ++i)
{
int sockfd = _revs[i].data.fd; // 前面刚连接的时候,已经设置进入内核 前面写入内核,这里从内核取回
uint32_t revents = _revs[i].events;
// 1. 将所有的异常处理,统一转化成IO错误 2. 所有的IO异常,统一转换成为一个异常处理函数
if (revents & EPOLLERR)
revents |= (EPOLLIN | EPOLLOUT); // 1. 将所有的异常处理,统一转化成IO错误
if (revents & EPOLLHUP)
revents |= (EPOLLIN | EPOLLOUT); // 1. 将所有的异常处理,统一转化成IO错误
if (revents & EPOLLIN)
{
if (IsConnectionExits(sockfd))
;
// 不用再区分是listen监视 还是普通socket ,因为使用了继承 多态,虽然调用同一个函数 ,但会进行重写
_connections[sockfd]->Recver();
}
if (revents & EPOLLOUT)
{
// if(IsConnectionExits(sockfd)); //判断是真的就绪了,还是因为出错导致设置的
// _connections[sockfd]->Sender();
}
}
}
bash
void Start()
{
if (IsConnectionEmpty())
return;
_isrunning = true;
while (_isrunning)
{
int n = LoopOnce();
Distribute(n);
}
}
1.7 完善Listener的Rcver 注意阻塞与非阻塞
fd的非阻塞设置
bash
void SetNonBlock(int fd)
{
int fl=fcntl(fd,F_GETFD);
if(fl<0) return ;
fcntl(fd,F_SETFD,fl|O_NONBLOCK);
}
bash
void Recver()
{
// accept
// 新连接就绪了,你能保证只有 一个连接到来吗? 一次把所有的连接全部获取上来
// while, ET, sockfd设置为非阻塞!! ---- listensock本身设置为非阻塞
LOG(LogLevel::INFO) << "进入到了Listener的Recver模块...";
InetAddr client;
while (true)
{
int sockfd = _listensock->Accept(&client);
if (sockfd == -1)
{
LOG(LogLevel::ERROR) << " accept done";
break;
}
else if (sockfd == -2)
{
LOG(LogLevel::ERROR) << "continue";
continue;
}
else if (sockfd == -3)
{
LOG(LogLevel::ERROR)<<"err";
break;
}
else {
//说明此时就是普通的描述符了,那么跟Listener一样,channel也创建一个类,继承Connection
}
}
}
1.8 普通fd 回调指针立大功
就如上面等待注释所说,要对普通的fd就绪再进行封装
bash
#pragma once
#include<iostream>
#include"Connection.hpp"
#include"Log.hpp"
using namespace LogModule;
class Channel :public Connection{
public:
Channel(int sockfd,const InetAddr&client)
:_sockfd(sockfd),_client_addr(client){SetFd(sockfd);}
void Recver()override
{
LOG(LogLevel::INFO)<<"事件派发到Channel";
}
void Sender()override
{
}
void Excepter()override
{
}
~Channel(){
close(_sockfd); // 关闭了Channel的_sockfd
close(GetFd()); // 又关闭了Connection的_fd(与_sockfd是同一个值,重复关闭)
// 若某次_sockfd=0,就会意外关闭fd=0
}
private:
int _sockfd;
InetAddr _client_addr;
};
bash
else {
//说明此时就是普通的描述符了,那么跟Listener一样,channel也创建一个类,继承Connection
std::shared_ptr<Connection> coon=std::make_shared<Channel>(sockfd,client);
coon->SetEvent(EPOLLIN|EPOLLET);
GetOwner()->AddConnection(coon);
}
而经过回调后,listener channel eooll的关系

1.9 从缓冲区读取数据
bash
#pragma once
#include <iostream>
#include <functional>
#include "Connection.hpp"
#include "Log.hpp"
#define SIZE 1024
using namespace LogModule;
class Channel : public Connection
{
public:
Channel(int sockfd, const InetAddr &client)
: _sockfd(sockfd), _client_add(client)
{
SetNonBlock(_sockfd);
}
// 问题一:怎么保证本轮数据读完
// 问题二:即使你把本轮数据读完 你怎么知道是完整报文呢 如果有多个报文呢?粘包问题?
void Recver() override
{
LOG(LogLevel::INFO) << "事件派发到Channel";
char buffer[SIZE];
while (true)
{
buffer[0] = 0;
ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0); // 非阻塞读取
if (n > 0)
{
buffer[n] = 0;
_inbuffer += buffer;
}
else if (n == 0)
{
Excepter();
return;
}
else
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
if (errno == EINTR)
continue;
else
{
Excepter();
return;
}
}
LOG(LogLevel::DEBUG) << "Channel: Inbuffer:\n"
<< _inbuffer;
if (!_inbuffer.empty())
_outbuffer += _handler(_inbuffer); // 和protocol相关的匿名函数里面!
}
}
void Sender() override
{
}
void Excepter() override
{
}
int GetFd() override
{
return _sockfd;
}
~Channel()
{
close(_sockfd); // 关闭了Channel的_sockfd
// 若某次_sockfd=0,就会意外关闭fd=0
}
private:
int _sockfd;
std::string _inbuffer; // 充当缓冲区
std::string _outbuffer;
InetAddr _client_add;
};