#pragma once
#include "NoCopy.hpp"
#include "log.hpp"
#include <sys/epoll.h>
#include <cstring>
#include <cerrno>
class Epoller : public NoCopy
{
static const int size = 128;
public:
Epoller()
{
_epfd = epoll_create(size);
if(_epfd == -1){
lg(Fatal, "epoll_create error: %s", strerror(errno));
}
else{
lg(Info, "epoll_create success: %d", _epfd);
}
}
int EpollerWait(struct epoll_event revents[], int num)
{
int n = epoll_wait(_epfd, revents, num, _timeout);
return n;
}
int EpollerCtl(int oper, int sockfd, uint32_t event)
{
int n = 0;
if(oper == EPOLL_CTL_DEL)
{
n = epoll_ctl(_epfd, oper, sockfd, nullptr);
if(n != 0){
lg(Error, "epoll_ctl delete error!");
}
}
else
{
// EPOLL_CTL_MOD 或者 EPOLL_CTL_ADD
// 设置进内核的红黑树中
struct epoll_event ev;
ev.events = event;
ev.data.fd = sockfd; // 方便我们后面处理的时候知道是哪一个 fd 就绪了
n = epoll_ctl(_epfd, oper, sockfd, &ev);
if(n != 0){
lg(Error, "epoll_ctl error!");
}
}
return n;
}
~Epoller()
{
if(_epfd >= 0){
close(_epfd);
}
}
private:
int _epfd;
int _timeout = 1000;
};
接下来编写 epollSever.hpp,如下:
#pragma once
#include <iostream>
#include <memory>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "log.hpp"
#include "Epoller.hpp"
#include "NoCopy.hpp"
uint32_t EVENT_IN = (EPOLLIN);
uint32_t EVENT_OUT = (EPOLLOUT);
class EpollServer : public NoCopy
{
static const int maxevents = 64;
public:
EpollServer(uint16_t port)
:_port(port)
,_listenSocket_ptr(new Sock())
,_epoller_ptr(new Epoller())
{}
void Init()
{
_listenSocket_ptr->Socket();
_listenSocket_ptr->Bind(_port);
_listenSocket_ptr->Listen();
lg(Info, "create listen socket success: %d\n", _listenSocket_ptr->GetFd());
}
void Accepter()
{
std::string client_ip;
uint16_t client_port;
int sockfd = _listenSocket_ptr->Accept(&client_ip, &client_port);
if(sockfd > 0){
// 不能直接读取,而是将它添加到内核的红黑树中,让 epoll 关心即可
_epoller_ptr->EpollerCtl(EPOLL_CTL_ADD, sockfd, EVENT_IN);
lg(Info, "get a new link, client info@ %s:%d", client_ip.c_str(), client_port);
}
else{
return;
}
}
// for test
void Recver(int fd)
{
char buffer[1024];
ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
std::cout << "get a message: " << buffer << std::endl;
// write
std::string echo_str = "sever echo $ ";
echo_str += buffer;
write(fd, echo_str.c_str(), echo_str.size());
}
else if (n == 0)
{
lg(Info, "client quit, me too, close fd is: %d", fd);
// 先在内核红黑树中移除 fd,再关闭 fd
_epoller_ptr->EpollerCtl(EPOLL_CTL_DEL, fd, 0);
close(fd);
}
else
{
lg(Warning, "recv error, fd is: %d", fd);
_epoller_ptr->EpollerCtl(EPOLL_CTL_DEL, fd, 0);
close(fd);
}
}
void Dispatcher(struct epoll_event revs[], int num)
{
for(int i = 0; i < num; ++i){
uint32_t event = revs[i].events;
int fd = revs[i].data.fd;
if(event & EVENT_IN){
// 读事件就绪
if(fd == _listenSocket_ptr->GetFd()){
// 获取到一个新连接,连接管理器
Accepter();
}
else{
// 其它 fd 上的普通读取事件就绪
Recver(fd);
}
}
else if(event & EVENT_OUT){
// 写事件就绪
// ...
}
else{
// ...
}
}
}
void Start()
{
// 将 listenSocket 添加到 epoll 中
// 也就是将 listenSocket 和它所关心的事件添加到内核 epoll 模型中的红黑树中!
_epoller_ptr->EpollerCtl(EPOLL_CTL_ADD, _listenSocket_ptr->GetFd(), EVENT_IN);
struct epoll_event revs[maxevents];
while(true)
{
// 其中 n 最大是 maxevents
int n = _epoller_ptr->EpollerWait(revs, maxevents);
if(n > 0)
{
// 有事件就绪,分派事件
lg(Debug, "event happend, fd is: %d", revs[0].data.fd);
Dispatcher(revs, n);
}
else if(n == 0)
{
lg(Info, "time out...");
}
else
{
lg(Error, "epoll wait error");
}
}
}
~EpollServer()
{
_listenSocket_ptr->Close();
}
private:
std::shared_ptr<Sock> _listenSocket_ptr;
std::shared_ptr<Epoller> _epoller_ptr;
uint16_t _port;
};
我们上面两个模块都用到了 NoCopy 这个类,也就是禁止拷贝,代码如下:
#pragma once
class NoCopy
{
public:
NoCopy(){}
NoCopy(const NoCopy&) = delete;
const NoCopy& operator=(const NoCopy&) = delete;
};
5. epoll 的工作模式
(1)水平触发 Level Triggered 工作模式(LT 模式)
epoll 默认所处的工作模式就是 LT 模式。例如我们上面所写的简单的 epoll 服务器,每次有新的连接到来时,如果我们不处理它,epoll 会每次都通知我们有连接到来了。这种一旦有新的连接到来,或者有新的数据到来,上层如果不取走,底层就会一直通知上层,让上层把数据尽快取走,这种模式就叫做 LT 模式。就像示波器中的高电平,一直有效。
(2)边缘触发 Edge Triggered 工作模式(ET 模式)
而 ET 模式指的是,数据或者连接,从无到有,从有到多,变化的时候,才会通知我们一次。正是因为 ET 模式有这种特点,才会倒逼程序员每次通知都必须把本轮数据全部取走,怎么保证数据全部取走呢?所以就需要循环读取,直到读取出错!但是我们使用 read() 或者 recv() 在缓冲区中读取数据的时候,当缓冲区的数据没有了,因为它们的读取方式默认是阻塞的,所以此时就会阻塞,服务器就会被挂起!所以我们在 ET 模式下,所有的 fd 必须是要设置为非阻塞的!
(3)LT 和 ET 的对比
- select 和 poll 其实也是工作在 LT 模式下;epoll 既可以支持 LT,也可以支持 ET;
- 普遍地我们认为,ET 的工作模式比 LT 的工作模式通知效率更高,因为通知一次就可以倒逼上层把全部数据读取走。同时也看得出来 ET 模式的 IO 效率也更高,这也就意味着,TCP 会向对方通告一个更大的窗口,从而从概率上让对方一次给自己发送更多的数据!
- 所谓的 LT 模式和 ET 模式,本质就是向就绪队列中放入多个或者一个就绪的事件
- 但是 ET 模式就一定比 LT 模式的效率高吗?不一定!因为 LT 也可以将所有的 fd 设置为非阻塞,然后循环读取,也就是当通知一次的时候,就把数据全部取走了,就和 ET 一样了!所以谁的效率高不一定,要看具体的实现。
二、Reactor
1. 概念
我们在上面编写的 epoll 服务器的代码中,在其他普通的 fd 读取事件就绪时,也就是在 Recver() 中,读取是有问题的,因为我们不能区分每次读取上来的数据是一个完整的报文。另外还有其它各种问题,所以我们要对上面的代码使用 Reactor 的设计模式作修改。
所谓的 Reactor 是一种设计模式,翻译过来称为反应堆模式。用于处理事件驱动的系统中的并发操作。它提供了一种结构化的方式来处理输入事件,并将其分发给相应的处理程序。Reactor 模式通常用于网络编程中,特别是在服务器端应用程序中。
要进行正确的 IO 处理,就应该有如下的理解:在应用层一定存在大量的连接,每一个连接在应用层都叫做文件描述符。而在读取每一个文件描述符上的数据的时候,可能根本就没有读取完,此时我们就需要把该文件描述符上的数据临时保存起来。所以我们在写服务器的时候,我们要保证每一个文件描述符及其连接及其缓冲区,都是独立的!
2. 实现
(1)Epoller.hpp
Epoller.hpp 是对 epoll 的系统调用的封装,代码如下:
#pragma once
#include "NoCopy.hpp"
#include "log.hpp"
#include <sys/epoll.h>
#include <cstring>
#include <cerrno>
class Epoller : public NoCopy
{
static const int size = 128;
public:
Epoller()
{
_epfd = epoll_create(size);
if(_epfd == -1){
lg(Fatal, "epoll_create error: %s", strerror(errno));
}
else{
lg(Info, "epoll_create success: %d", _epfd);
}
}
int EpollerWait(struct epoll_event revents[], int num, int timeout)
{
int n = epoll_wait(_epfd, revents, num, timeout);
return n;
}
int EpollerCtl(int oper, int sockfd, uint32_t event)
{
int n = 0;
if(oper == EPOLL_CTL_DEL)
{
n = epoll_ctl(_epfd, oper, sockfd, nullptr);
if(n != 0){
lg(Error, "epoll_ctl delete error! sockfd: %d", sockfd);
}
}
else
{
// EPOLL_CTL_MOD 或者 EPOLL_CTL_ADD
// 设置进内核的红黑树中
struct epoll_event ev;
ev.events = event;
ev.data.fd = sockfd; // 方便我们后面处理的时候知道是哪一个 fd 就绪了
n = epoll_ctl(_epfd, oper, sockfd, &ev);
if(n != 0){
lg(Error, "epoll_ctl error!");
}
}
return n;
}
~Epoller()
{
if(_epfd >= 0){
close(_epfd);
}
}
private:
int _epfd;
int _timeout = 1000;
};
(2)TcpServer.hpp
TcpServer.hpp 是处理 IO 的服务器,代码如下:
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
#include <functional>
#include <cerrno>
#include "log.hpp"
#include "NoCopy.hpp"
#include "Epoller.hpp"
#include "Socket.hpp"
#include "Comm.hpp"
// 设置 ET 模式
uint32_t EVENT_IN = (EPOLLIN | EPOLLET);
uint32_t EVENT_OUT = (EPOLLOUT | EPOLLET);
const static int g_buffer_size = 128;
class Connection;
class TcpServer;
using func_t = std::function<void(std::weak_ptr<Connection>)>;
using except_t = std::function<void(std::weak_ptr<Connection>)>;
// 管理每一个连接
class Connection
{
public:
Connection(int sockfd)
:_sockfd(sockfd)
{}
void SetHandler(func_t recv_cb, func_t send_cb, except_t except_cb)
{
_recv_cb = recv_cb;
_send_cb = send_cb;
_except_cb = except_cb;
}
~Connection()
{}
private:
int _sockfd;
// 充当缓冲区
std::string _inbuffer;
std::string _outbuffer;
public:
// 回指指针
// std::shared_ptr<TcpServer> _tcpServer_ptr;
std::weak_ptr<TcpServer> _tcpServer_ptr;
// 回调方法
func_t _recv_cb;
func_t _send_cb;
except_t _except_cb;
std::string _ip;
uint16_t _port;
int Sockfd()
{
return _sockfd;
}
void AppendInBuffer(const std::string& info)
{
_inbuffer += info;
}
void AppendOutBuffer(const std::string& info)
{
_outbuffer += info;
}
std::string& Inbuffer()
{
return _inbuffer;
}
std::string& Outbuffer()
{
return _outbuffer;
}
void SetWeakPtr(std::weak_ptr<TcpServer> tcpServer_ptr)
{
_tcpServer_ptr = tcpServer_ptr;
}
};
// enable_shared_from_this 可以提供返回当前对象的 this 对应的 shared_ptr
class TcpServer : public std::enable_shared_from_this<TcpServer>, public NoCopy
{
static const int num = 64;
public:
TcpServer(uint16_t port, func_t OnMessage)
:_port(port)
,_quit(true)
,_OnMessage(OnMessage)
,_epoller_ptr(new Epoller())
,_listenSock_ptr(new Sock())
{}
void Init()
{
_listenSock_ptr->Socket();
// 将 fd 设置为非阻塞
SetNonBlockOrDie(_listenSock_ptr->GetFd());
_listenSock_ptr->Bind(_port);
_listenSock_ptr->Listen();
lg(Info, "create listen socket success: %d\n", _listenSock_ptr->GetFd());
AddConnection(_listenSock_ptr->GetFd(), EVENT_IN, \
std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);
}
void AddConnection(int sockfd, uint32_t event, func_t recv_cb, func_t send_cb, except_t except_cb,\
const std::string& ip = "0.0.0.0", uint16_t port = 0)
{
// 1. 给所有的套接字建立一个 connection 对象
std::shared_ptr<Connection> new_connection(new Connection(sockfd));
// shared_from_this(): 返回当前对象的 shared_ptr
new_connection->SetWeakPtr(shared_from_this());
new_connection->SetHandler(recv_cb, send_cb, except_cb);
new_connection->_ip = ip;
new_connection->_port = port;
// 2. 将套接字和 Connection 添加到 unordered_map 中
_connections.insert(std::make_pair(sockfd, new_connection));
// 3. 将 listen 套接字或其它事件添加到 epoll 模型中
_epoller_ptr->EpollerCtl(EPOLL_CTL_ADD, sockfd, event);
lg(Debug, "add a new connection success, sockfd is: %d", sockfd);
}
// listen 套接字的连接管理器,即有事件就绪的时候,就是有连接到来,就需要处理新连接
void Accepter(std::weak_ptr<Connection> conn)
{
auto connection = conn.lock();
// 不断检测是否还有新连接,直到读取出错
while(true){
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = ::accept(connection->Sockfd(), (sockaddr*)&peer, &len);
// 获取到新连接设置为非阻塞,然后构建 Connection 对象放入哈希表中和内核红黑树中
if(sockfd > 0){
uint16_t peer_port = ntohs(peer.sin_port);
char ipbuffer[128];
inet_ntop(AF_INET, &peer.sin_addr, ipbuffer, sizeof(ipbuffer));
lg(Debug, "get a new client, get info-> [%s: %d], sockfd: %d", ipbuffer, peer_port, sockfd);
SetNonBlockOrDie(sockfd);
AddConnection(sockfd, EVENT_IN, \
std::bind(&TcpServer::Recver, this, std::placeholders::_1),\
std::bind(&TcpServer::Sender, this, std::placeholders::_1),\
std::bind(&TcpServer::Excepter, this, std::placeholders::_1),\
ipbuffer, peer_port);
}
else{
if(errno == EWOULDBLOCK){ // 读取完毕
break;
}
else if(errno == EINTR){ // 信号原因中断
continue;
}
else{
break;
}
}
}
}
// 普通事件的事件管理器
// 对于服务器而言只需要进行IO,不需要关心是否读完和报文的格式
void Recver(std::weak_ptr<Connection> conn)
{
if(conn.expired()) return;
auto connection = conn.lock();
int sockfd = connection->Sockfd();
while(true){
char buffer[g_buffer_size];
memset(buffer, 0, sizeof(buffer));
ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // 非阻塞读取
if(n > 0){
connection->AppendInBuffer(buffer);
}
else if(n == 0){
lg(Info, "sockfd: %d, client info %s: %d quit...", sockfd, connection->_ip.c_str(), connection->_port);
connection->_except_cb(connection);
}
else{
if(errno == EWOULDBLOCK){
break;
}
else if(errno == EINTR){
continue;
}
else{
lg(Warning, "sockfd: %d, client info %s: %d recv error...", sockfd, connection->_ip.c_str(), connection->_port);
connection->_except_cb(connection);
return;
}
}
}
// 交给上层处理,读取到的数据都在 connection 中
// 1. 检测
// 2. 如果有完整报文,就处理
_OnMessage(connection);
}
void Sender(std::weak_ptr<Connection> conn)
{
if(conn.expired()) return;
auto connection = conn.lock();
auto& outbuffer = connection->Outbuffer();
while(true){
ssize_t n = send(connection->Sockfd(), outbuffer.c_str(), outbuffer.size(), 0);
if(n > 0){
outbuffer.erase(0, n);
if(outbuffer.empty()){
break;
}
}
else if(n == 0){
return;
}
else{
if(errno == EWOULDBLOCK){
break;
}
else if(errno == EINTR){
continue;
}
else{
lg(Warning, "sockfd: %d, client info %s: %d send error...", connection->Sockfd(), connection->_ip.c_str(), connection->_port);
connection->_except_cb(connection);
return;
}
}
}
// 没发完
if(!outbuffer.empty()){
// 开始对写事件关心
EnableEvent(connection->Sockfd(), true, true);
}
else{
// 关闭对写事件关心
EnableEvent(connection->Sockfd(), true, false);
}
}
void Excepter(std::weak_ptr<Connection> connection)
{
if(connection.expired()) return;
auto conn = connection.lock();
lg(Debug, "Excepter hander sockfd: %d, client info %s: %d excepter handler", \
conn->Sockfd(), conn->_ip.c_str(), conn->_port);
// 1. 移除对特定 fd 的关心
// EnableEvent(connection->Sockfd(), false, false);
_epoller_ptr->EpollerCtl(EPOLL_CTL_DEL, conn->Sockfd(), 0);
// 2. 关闭异常的 fd
lg(Debug, "close %d done...\n", conn->Sockfd());
close(conn->Sockfd());
// 3. 从 _connections 中移除 fd 和 Connection 的映射关系
lg(Debug, "remove %d from _connections...\n", conn->Sockfd());
_connections.erase(conn->Sockfd());
}
void EnableEvent(int sockfd, bool readAble, bool writeAble)
{
uint32_t events = 0;
events |= ((readAble ? EPOLLIN : 0) | (writeAble ? EPOLLOUT : 0) | EPOLLET);
_epoller_ptr->EpollerCtl(EPOLL_CTL_MOD, sockfd, events);
}
bool IsConnectionExist(int fd)
{
auto iter = _connections.find(fd);
if(iter == _connections.end()){
return false;
}
else{
return true;
}
}
void Dispatcher(int timeout)
{
int n = _epoller_ptr->EpollerWait(revs, num, timeout);
for(int i = 0; i < n; i++){
uint32_t event = revs[i].events;
int sockfd = revs[i].data.fd;
// 一旦事件异常,统一把异常转换为读写问题
if(event & EPOLLERR){
event |= (EPOLLIN | EPOLLOUT);
}
if(event & EPOLLHUP){
event |= (EPOLLIN | EPOLLOUT);
}
// 只需要处理读写
if((event & EPOLLIN) && IsConnectionExist(sockfd)){
if(_connections[sockfd]->_recv_cb){
_connections[sockfd]->_recv_cb(_connections[sockfd]);
}
}
if((event & EPOLLOUT) && IsConnectionExist(sockfd)){
if(_connections[sockfd]->_send_cb){
_connections[sockfd]->_send_cb(_connections[sockfd]);
}
}
}
}
void Loop()
{
_quit = false;
// AddConnection();
while(!_quit)
{
// 事件派发
// Dispatcher(3000);
Dispatcher(-1);
PrintConnection();
}
_quit = true;
}
void PrintConnection()
{
std::cout << "_connections fd list: ";
for(auto& connection : _connections){
std::cout << connection.second->Sockfd() << ", ";
std::cout << "inbuffer: " << connection.second->Inbuffer() << " ";
}
std::cout << std::endl;
}
~TcpServer()
{}
private:
std::shared_ptr<Epoller> _epoller_ptr;
std::shared_ptr<Sock> _listenSock_ptr;
uint16_t _port;
bool _quit;
struct epoll_event revs[num];
// fd 到对应连接到映射,_connections 就是当前服务器管理的所有连接
std::unordered_map<int, std::shared_ptr<Connection>> _connections;
// 让上层处理信息
func_t _OnMessage;
};
(3)Calculator.hpp
Calculator.hpp 是上层处理业务的具体处理方法,代码如下:
#pragma once
#include <string>
#include <iostream>
#include "Protocol.hpp"
enum
{
DIV_ERR = 1,
MOD_ERR = 2,
OP_ERR = 3
};
// 上层业务
class Calculator
{
public:
Calculator()
{}
Response CalculatorHelper(const Request &req)
{
Response resp(0, 0);
switch (req._op)
{
case '+':
resp._result = req._x + req._y;
还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!
王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。
对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!
【完整版领取方式在文末!!】
***93道网络安全面试题***



内容实在太多,不一一截图了
### 黑客学习资源推荐
最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
😝朋友们如果有需要的话,可以联系领取~
#### 1️⃣零基础入门
##### ① 学习路线
对于从来没有接触过网络安全的同学,我们帮你准备了详细的**学习成长路线图**。可以说是**最科学最系统的学习路线**,大家跟着这个大的方向学习准没问题。

##### ② 路线对应学习视频
同时每个成长路线对应的板块都有配套的视频提供:

#### 2️⃣视频配套工具&国内外网安书籍、文档
##### ① 工具

##### ② 视频

##### ③ 书籍

资源较为敏感,未展示全面,需要的最下面获取

##### ② 简历模板

**因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆**