多路转接epoll
目录
定位:基于等待多个文件描述符就绪事件的通知机制,为了处理大批量句柄而作了改进的poll
一、epoll接口
1.1.epoll_create函数
作用:创建一个epoll模型
参数:任意大于0的数(已经被忽略)
返回值:文件描述符

1.2.epoll_ctrl函数
作用:控制epoll的文件描述符(用户 → 内核)
参数1:epoll_create的返回值
参数2:设置操作类型

参数3:需要监听的文件描述符
参数4:设置监听事件,附带用户数据

events对应以下几个宏集合
- EPOLLIN : 表示对应的文件描述符可以读
- EPOLLOUT : 表示对应的文件描述符可以写
- EPOLLPRI : 表示对应的文件描述符有紧急的数据可读
- EPOLLERR : 表示对应的文件描述符发⽣错误
- EPOLLHUP : 表示对应的文件描述符被挂断
- EPOLLET : 将EPOLL设为边缘触发模式
- EPOLLONESHOT:只监听⼀次事件,当监听完这次事件之后如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL红黑树里
返回值:
- 成功:0
- 失败:-1

1.3.epoll_wait函数
作用:控制epoll的文件描述符(内核 → 用户)
参数1:epoll_create的返回值
参数2:数组
参数3:数组长度
参数4:设置超时时间,单位为毫秒

二、epoll原理
2.1.示意图
红黑树本质:用户告诉内核,要关心哪个文件描述符,哪一个事件
就绪队列本质:内核告诉用户,哪一个文件描述符的哪些事件就绪
网络协议栈中的回调机制:一旦底层特定的文件描述符有数据就绪,就会自动进行回调
激活红黑树中的节点到就绪队列中,该机制被提前注册到文件描述符中

内核会严格按照零下标开始,依次拷贝保存就绪事件和文件描述符
应用层在处理就绪事件的时候,处理的都是就绪的,不用进行检测
2.2.内核结构


通过private_data指针找到eventpoll结构

2.3.回调机制
- sys_epoll_ctl
cpp
/*
* The following function implements the controller interface for
* the eventpoll file that enables the insertion/removal/change of
* file descriptors inside the interest set. It represents
* the kernel part of the user space epoll_ctl(2).
*/
asmlinkage long
sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event)
{
int error;
struct file *file, *tfile;
struct eventpoll *ep;
struct epitem *epi;
struct epoll_event epds;
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)\n",
current, epfd, op, fd, event));
error = -EFAULT;
if (ep_op_hash_event(op) &&
copy_from_user(&epds, event, sizeof(struct epoll_event)))
goto eexit_1;
/* Get the "struct file *" for the eventpoll file */
error = -EBADF;
file = fget(epfd);
if (!file)
goto eexit_1;
/* Get the "struct file *" for the target file */
tfile = fget(fd);
if (!tfile)
goto eexit_2;
/* The target file descriptor must support poll */
error = -EPERM;
if (!tfile->f_op || !tfile->f_op->poll)
goto eexit_3;
/*
* We have to check that the file structure underneath the file descriptor
* the user passed to us _is_ an eventpoll file. And also we do not permit
* adding an epoll file descriptor inside itself.
*/
error = -EINVAL;
if (file == tfile || !is_file_epoll(file))
goto eexit_3;
/*
* At this point it is safe to assume that the "private_data" contains
* our own data structure.
*/
ep = file->private_data;
down_write(&ep->sem);
/* Try to lookup the file inside our hash table */
epi = ep_find(ep, tfile, fd);
error = -EINVAL;
switch (op) {
case EPOLL_CTL_ADD:
if (!epi) {
epds.events |= POLLERR | POLLHUP;
error = ep_insert(ep, &epds, tfile, fd);
} else
error = -EEXIST;
break;
case EPOLL_CTL_DEL:
if (epi)
error = ep_remove(ep, epi);
else
error = -ENOENT;
break;
case EPOLL_CTL_MOD:
if (epi) {
epds.events |= POLLERR | POLLHUP;
error = ep_modify(ep, epi, &epds);
} else
error = -ENOENT;
break;
}
/*
* The function ep_find() increments the usage count of the structure
* so, if this is not NULL, we need to release it.
*/
if (epi)
ep_release_epitem(epi);
up_write(&ep->sem);
eexit_3:
fput(tfile);
eexit_2:
fput(file);
eexit_1:
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p) = %d\n",
current, epfd, op, fd, event, error));
return error;
}
- ep_insert
cpp
static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)
{
int error, revents, pwake = 0;
unsigned long flags;
struct epitem *epi;
struct ep_pqueue epq;
error = -ENOMEM;
if (!(epi = kmem_cache_alloc(epi_cache, SLAB_KERNEL)))
goto eexit_1;
/* Item initialization follow here ... */
ep_rb_initnode(&epi->rbn);
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->txlink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->ep = ep;
ep_set_ffd(&epi->ffd, tfile, fd);
epi->event = *event;
atomic_set(&epi->usecnt, 1);
epi->nwait = 0;
/* Initialize the poll table using the queue callback */
epq.epi = epi;
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
/*
* Attach the item to the poll hooks and get current event bits.
* We can safely use the file* here because its usage count has
* been increased by the caller of this function.
*/
revents = tfile->f_op->poll(tfile, &epq.pt);
/*
* We have to check if something went wrong during the poll wait queue
* install process. Namely an allocation for a wait queue failed due
* high memory pressure.
*/
if (epi->nwait < 0)
goto eexit_2;
/* Add the current item to the list of active epoll hook for this file */
spin_lock(&tfile->f_ep_lock);
list_add_tail(&epi->fllink, &tfile->f_ep_links);
spin_unlock(&tfile->f_ep_lock);
/* We have to drop the new item inside our item list to keep track of it */
write_lock_irqsave(&ep->lock, flags);
/* Add the current item to the rb-tree */
ep_rbtree_insert(ep, epi);
/* If the file is already "ready" we drop it inside the ready list */
if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
/* Notify waiting tasks that events are available */
if (waitqueue_active(&ep->wq))
__wake_up_locked(&ep->wq, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE);
if (waitqueue_active(&ep->poll_wait))
pwake++;
}
write_unlock_irqrestore(&ep->lock, flags);
/* We have to call this outside the lock */
if (pwake)
ep_poll_safewake(&psw, &ep->poll_wait);
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_insert(%p, %p, %d)\n", current, ep, tfile, fd));
return 0;
eexit_2:
ep_unregister_pollwait(ep, epi);
/*
* We need to do this because an event could have been arrived on some
* allocated wait queue.
*/
write_lock_irqsave(&ep->lock, flags);
if (ep_is_linked(&epi->rdllink))
ep_list_del(&epi->rdllink);
write_unlock_irqrestore(&ep->lock, flags);
kmem_cache_free(epi_cache, epi);
eexit_1:
return error;
}
- ep_ptable_queue_proc
cpp
/*
* This is the callback that is used to add our wait queue to the
* target file wakeup lists.
*/
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt)
{
struct epitem *epi = ep_item_from_epqueue(pt);
struct eppoll_entry *pwq;
if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, SLAB_KERNEL))) {
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
pwq->whead = whead;
pwq->base = epi;
add_wait_queue(whead, &pwq->wait);
list_add_tail(&pwq->llink, &epi->pwqlist);
epi->nwait++;
} else {
/* We have to signal that an error occurred */
epi->nwait = -1;
}
}
- ep_poll_callback
cpp
/*
* This is the callback that is passed to the wait queue wakeup
* machanism. It is called by the stored file descriptors when they
* have events to report.
*/
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
int pwake = 0;
unsigned long flags;
struct epitem *epi = ep_item_from_wait(wait);
struct eventpoll *ep = epi->ep;
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: poll_callback(%p) epi=%p ep=%p\n",
current, epi->ffd.file, epi, ep));
write_lock_irqsave(&ep->lock, flags);
/*
* If the event mask does not contain any poll(2) event, we consider the
* descriptor to be disabled. This condition is likely the effect of the
* EPOLLONESHOT bit that disables the descriptor when an event is received,
* until the next EPOLL_CTL_MOD will be issued.
*/
if (!(epi->event.events & ~EP_PRIVATE_BITS))
goto is_disabled;
/* If this file is already in the ready list we exit soon */
if (ep_is_linked(&epi->rdllink))
goto is_linked;
list_add_tail(&epi->rdllink, &ep->rdllist);
is_linked:
/*
* Wake up ( if active ) both the eventpoll wait list and the ->poll()
* wait list.
*/
if (waitqueue_active(&ep->wq))
__wake_up_locked(&ep->wq, TASK_UNINTERRUPTIBLE |
TASK_INTERRUPTIBLE);
if (waitqueue_active(&ep->poll_wait))
pwake++;
is_disabled:
write_unlock_irqrestore(&ep->lock, flags);
/* We have to call this outside the lock */
if (pwake)
ep_poll_safewake(&psw, &ep->poll_wait);
return 1;
}
2.4.epoll的特点
接口使用方便:拆分成三个函数,分离输入输出参数,不需要循环设置要关注的文件描述符
数据拷贝轻量:只需在合适的时候调用EPOLL_CTL_ADD将文件描述符拷贝到内核
事件回调机制:避免遍历,使用回调方式将就绪的文件描述符从红黑树加入到就绪队列
没有数量限制:文件描述符数量没有上限
三、Epoll服务器实现
3.1.EpollServer.hpp
cpp
#pragma once
#include <iostream>
#include <memory>
#include <unistd.h>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "Log.hpp"
using namespace SocketModule;
using namespace LogModule;
class EpollServer
{
const static int size = 64;
const static int defaultfd = -1;
public:
EpollServer(int port)
: _listensock(std::make_unique<TcpSocket>()), _isrunning(false), _epfd(defaultfd)
{
// 创建监听套接字 fd = 3
_listensock->BuildTcpSocketMethod(port);
// 创建epoll模型 fd = 4
_epfd = epoll_create(256);
if (_epfd < 0)
{
LOG(LogLevel::FATAL) << "epoll_create error";
exit(EPOLL_CREATE_ERR);
}
LOG(LogLevel::INFO) << "epoll_create success: " << _epfd;
// 将监听套接字设置到内核
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = _listensock->Fd();
int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock->Fd(), &ev);
if (n < 0)
{
LOG(LogLevel::FATAL) << "add listensockfd failed";
exit(EPOLL_CTL_ERR);
}
}
void Start()
{
int timeout = 1000; // ms
_isrunning = true;
while (_isrunning)
{
int n = epoll_wait(_epfd, _revs, size, timeout);
switch(n)
{
case 0:
LOG(LogLevel::DEBUG) << "time out...";
break;
case -1:
LOG(LogLevel::FATAL) << "epoll error";
break;
default:
Dispatcher(n);
break;
}
}
_isrunning = false;
}
// 事件派发器
void Dispatcher(int rnum)
{
LOG(LogLevel::DEBUG) << "event ready ..."; // LT: 水平触发模式
for(int i = 0; i < rnum; i++)
{
int sockfd = _revs[i].data.fd;
uint32_t revent = _revs[i].events;
// 循环处理就绪事件
if(revent & EPOLLIN)
{
// 读事件就绪
if(sockfd == _listensock->Fd())
{
// 获取新连接
Accepter();
}
else
{
// 普通套接字可读
Recver(sockfd);
}
}
// if(revent & EPOLLOUT)
// {
// // 写事件就绪
// }
}
}
// IO处理器
void Recver(int sockfd)
{
char buffer[1024];
ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
std::cout << "client say@ " << buffer << std::endl;
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "client quit...";
// 从epoll中移除关心的文件描述符
// 先移除, 再关闭, 确保移除合法fd
int ret = epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr);
if(ret > 0)
{
LOG(LogLevel::INFO) << "remove sockfd success: " << sockfd;
}
close(sockfd);
}
else
{
LOG(LogLevel::ERROR) << "recv error";
int ret = epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr);
if(ret > 0)
{
LOG(LogLevel::INFO) << "remove sockfd success: " << sockfd;
}
close(sockfd);
}
}
// 连接管理器
void Accepter()
{
InetAddr client;
int sockfd = _listensock->Accept(&client);
if (sockfd >= 0)
{
// 获取新连接
LOG(LogLevel::INFO) << "get a new link, sockfd: " << sockfd << ", client is " << client.StringAddr();
// 将新的套接字添加到内核
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);
if(n < 0)
{
LOG(LogLevel::WARNING) << "add sockfd failed";
}
else
{
LOG(LogLevel::INFO) << "add sockfd success: " << sockfd;
}
}
}
void Stop()
{
_isrunning = false;
}
~EpollServer()
{
_listensock->Close();
if (_epfd > 0)
{
close(_epfd);
}
}
private:
std::unique_ptr<Socket> _listensock;
bool _isrunning;
int _epfd; // epoll模型
struct epoll_event _revs[size]; // 就绪事件数组
};
3.2.Main.cc
cpp
#include "EpollServer.hpp"
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cout << "Usage: " << argv[0] << " port" << std::endl;
exit(USAGE_ERR);
}
Enable_Console_Log_Strategy();
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<EpollServer> svr = std::make_unique<EpollServer>(port);
svr->Start();
return 0;
}
3.3.Common.hpp
cpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
enum ExitCode
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
FORK_ERR,
OPEN_ERR,
EPOLL_CREATE_ERR,
EPOLL_CTL_ERR
};
// 禁止拷贝
class NoCopy
{
public:
NoCopy()
{
}
~NoCopy()
{
}
NoCopy(const NoCopy &) = delete;
const NoCopy &operator=(const NoCopy &) = delete;
};
#define CONV(addr) ((struct sockaddr *)&addr)
四、LT与ET模式
4.1.LT水平触发模式
水平触发:示波器长时间保持高电平或者低电平
**LT模式(Level Triggered):**水平触发工作模式(默认)
只要底层有报文,就要一直通知上层
上层知道还会通知,所以可以不读完
4.2.ET边缘触发模式
边缘触发:示波器从高电平变化为低电平,或者从低电平变化为高电平
**ET模式(Edge Triggered):**边缘触发工作模式
只有底层的数据发生从无到有,从有到多的变化时,才会通知上层
即使上层只把数据拿走一部分,后来数据没有新增,ET也不会通知
循环读取 + 非阻塞IO
ET通知就绪,用户必须通过循环读取本轮缓冲区的全部数据
recv读完缓冲区数据,用户并不清楚,继续调用recv会被阻塞
所以在ET模式下,必须将文件描述符设置为非阻塞的工作模式
LT VS ET
LT模式可以实现非阻塞轮询形式,但必须对程序员进行约束
ET模式则是通过操作系统来约束,增加IO读写方式的确定性
ET通知效率更高,有效通知数量最多
ET尽快读完所有数据,能给对方更新一个更大的窗口
提高对方滑动窗口大小,提高TCP传输效率,提高网络发送报文的并发度
PSH标志位本质:提醒上层读取数据
五、Reactor反应堆模式
5.1.模式框图

读事件的关心需要常设,写事件的关心需要按需设置
如果写事件设置为常设,epoll就会一直让写事件就绪
当把发送缓冲区写满后,再向epoll设置写事件的关心
手动开启EPOLLOUT,默认就要触发一次写事件
5.2.Epoller.hpp
cpp
#pragma once
#include <iostream>
#include <sys/epoll.h>
#include "Log.hpp"
#include "Common.hpp"
using namespace LogModule;
class Epoller
{
public:
Epoller()
: _epfd(-1)
{
_epfd = epoll_create(128);
if (_epfd < 0)
{
LOG(LogLevel::FATAL) << "epoll_create error";
exit(EPOLL_CREATE_ERR);
}
LOG(LogLevel::INFO) << "create epoll success: " << _epfd;
}
void ModEventHelper(int sockfd, uint32_t events, int oper)
{
struct epoll_event ev;
ev.events = events;
ev.data.fd = sockfd;
int n = epoll_ctl(_epfd, oper, sockfd, &ev);
if (n < 0)
{
LOG(LogLevel::ERROR) << "epoll_ctl error";
return;
}
LOG(LogLevel::INFO) << "epoll_ctl success: " << sockfd;
}
void AddEvent(int sockfd, uint32_t events)
{
ModEventHelper(sockfd, events, EPOLL_CTL_ADD);
}
void ModEvent(int sockfd, uint32_t events)
{
ModEventHelper(sockfd, events, EPOLL_CTL_MOD);
}
void DelEvent(int sockfd)
{
int n = epoll_ctl(sockfd, EPOLL_CTL_DEL, sockfd, nullptr);
(void)n;
}
int WaitEvents(struct epoll_event revs[], int maxnum, int timeout)
{
int n = epoll_wait(_epfd, revs, maxnum, timeout);
if (n < 0)
{
LOG(LogLevel::WARNING) << "epoll_wait error";
}
else if (n == 0)
{
LOG(LogLevel::WARNING) << "epoll_wait timeout";
}
else
{
// TODO
}
return n;
}
~Epoller()
{
if (_epfd >= 0)
{
close(_epfd);
}
}
private:
int _epfd;
};
5.3.Reactor.hpp
cpp
#pragma once
#include <iostream>
#include <memory>
#include <unordered_map>
#include "Epoller.hpp"
#include "Connection.hpp"
#include "Log.hpp"
using namespace LogModule;
// 反应堆
class Reactor
{
static const int revs_num = 128;
private:
bool IsConnectionExitsHelper(int sockfd)
{
auto iter = _connections.find(sockfd);
if (iter == _connections.end())
{
return false;
}
else
{
return true;
}
}
bool IsConnectionExits(const std::shared_ptr<Connection> &conn)
{
return IsConnectionExitsHelper(conn->GetSockFd());
}
bool IsConnectionExits(int sockfd)
{
return IsConnectionExitsHelper(sockfd);
}
bool IsConnectionEmpty()
{
return _connections.empty();
}
int LoopOnce(int timeout)
{
return _epoller_ptr->WaitEvents(_revs, revs_num, timeout);
}
// 事件派发器
void Dispatcher(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);
}
if (revents & EPOLLHUP)
{
// 处理连接
revents |= (EPOLLIN | EPOLLOUT);
}
if (revents & EPOLLIN)
{
// 读事件就绪
if (IsConnectionExits(sockfd))
{
_connections[sockfd]->Recver();
}
}
if (revents & EPOLLOUT)
{
// 写事件就绪
if (IsConnectionExits(sockfd))
{
_connections[sockfd]->Sender();
}
}
}
}
public:
Reactor()
: _epoller_ptr(std::make_unique<Epoller>()), _isrunning(false)
{
}
void Loop()
{
if (IsConnectionEmpty())
{
return;
}
_isrunning = true;
int timeout = -1;
while (_isrunning)
{
PrintConnection(); // debug
int n = LoopOnce(timeout);
Dispatcher(n);
}
_isrunning = false;
}
// 将新连接添加到哈希表, 写入到epoll内核
void AddConnection(std::shared_ptr<Connection> &conn)
{
// 0. 不要重复添加连接
if (IsConnectionExits(conn))
{
LOG(LogLevel::WARNING) << "conn is exits: " << conn->GetSockFd();
return;
}
// 1. conn对应的fd与关心事件写入epoll内核
uint32_t events = conn->GetEvent();
int sockfd = conn->GetSockFd();
_epoller_ptr->AddEvent(sockfd, events);
// 2. 设置conn的回指指针
conn->SetOwner(this);
// 3. conn对象添加到哈希表中
_connections[sockfd] = conn;
}
// 设置对读写事件的关心
void EnableReadWrite(int sockfd, bool enableread, bool enablewrite)
{
// 判断文件描述符是否存在
if (!IsConnectionExits(sockfd))
{
LOG(LogLevel::WARNING) << "EnableReadWrite, conn is exists: " << sockfd;
return;
}
// 修改当前文件描述符对应的关心事件
uint32_t new_event = (EPOLLET | (enableread ? EPOLLIN : 0) | (enablewrite ? EPOLLOUT : 0));
_connections[sockfd]->SetEvent(new_event);
// 写透内核, 调整文件描述符对特定事件的关心
_epoller_ptr->ModEvent(sockfd, new_event);
}
// 删除连接
void DelConnection(int sockfd)
{
// epoll移除的时候, sockfd必须合法
_epoller_ptr->DelEvent(sockfd);
// 从哈希表中移除自己
_connections.erase(sockfd);
// 关闭不需要的文件描述符
close(sockfd);
LOG(LogLevel::INFO) << "client quit: " << sockfd;
}
void PrintConnection()
{
std::cout << "当前Reactor正在管理的fd List: ";
for(auto &conn : _connections)
{
std::cout << conn.second->GetSockFd() << " ";
}
std::cout << "\r\n";
}
void Stop()
{
_isrunning = false;
}
~Reactor()
{
}
private:
std::unique_ptr<Epoller> _epoller_ptr; // epoll模型
std::unordered_map<int, std::shared_ptr<Connection>> _connections; // 管理所有的连接
bool _isrunning; // 启动标志位
struct epoll_event _revs[revs_num]; // 就绪事件存储数组
};
5.4.Connection.hpp
cpp
#pragma once
// 封装文件描述符, 保证给每一个文件描述符一套缓冲区
#include <iostream>
#include <string>
#include <functional>
#include "InetAddr.hpp"
class Reactor;
class Connection;
using handler_t = std::function<std::string(std::string &)>;
// 基类
class Connection
{
public:
Connection()
: _events(0), _owner(nullptr)
{
}
virtual void Recver() = 0;
virtual void Sender() = 0;
virtual void Excepter() = 0;
virtual int GetSockFd() = 0;
void RegisterHandler(handler_t handler)
{
_handler = handler;
}
void SetEvent(const uint32_t &events)
{
_events = events;
}
uint32_t GetEvent()
{
return _events;
}
void SetOwner(Reactor *owner)
{
_owner = owner;
}
Reactor *GetOwner()
{
return _owner;
}
~Connection()
{
}
private:
uint32_t _events; // 关心事件
Reactor *_owner; // 回指指针
public:
handler_t _handler; // 回调方法
};
5.5.Listener.hpp
cpp
#pragma once
#include <iostream>
#include <memory>
#include "Epoller.hpp"
#include "Socket.hpp"
#include "Common.hpp"
#include "Connection.hpp"
#include "Channel.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 | EPOLLET);
SetNonBlock(_listensock->Fd());
}
void Recver() override
{
// LOG(LogLevel::DEBUG) << "进入Listener模块的Recver函数";
// ET模式下, 循环获取全部连接, 监听套接字设置为非阻塞
InetAddr client;
while (true)
{
int sockfd = _listensock->Accept(&client);
if (sockfd == ACCEPT_ERR)
{
break;
}
else if (sockfd == ACCEPT_CONTINUE)
{
continue;
}
else if (sockfd == ACCEPT_DONE)
{
break;
}
else
{
// 连接获取成功
// 普通的文件描述符
std::shared_ptr<Connection> conn = std::make_shared<Channel>(sockfd, client);
conn->SetEvent(EPOLLIN | EPOLLET);
if (_handler != nullptr)
{
conn->RegisterHandler(_handler);
}
GetOwner()->AddConnection(conn);
}
}
}
int GetSockFd() override
{
return _listensock->Fd();
}
void Sender() override
{
}
void Excepter() override
{
}
~Listener()
{
}
private:
int _port;
std::unique_ptr<Socket> _listensock;
};
5.6.Channel.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory>
#include <functional>
#include "Common.hpp"
#include "Connection.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
#define SIZE 1024
class Channel;
// 普通套接字封装
class Channel : public Connection
{
public:
Channel(int sockfd, const InetAddr &client)
: _sockfd(sockfd), _client_addr(client)
{
SetNonBlock(_sockfd);
}
void Recver() override
{
char buffer[SIZE];
while (true) // 循环读取, 保证本轮事件数据读完(本层只解决IO问题)
{
// 粘包问题与序列反序列化问题(引入自定义协议, 由应用层解决)
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;
}
else if (errno == EINTR)
{
// 被信号中断
continue;
}
else
{
// 读取出错
Excepter(); // 异常处理
return;
}
}
}
LOG(LogLevel::DEBUG) << "Channel Inbuffer: " << _inbuffer;
if (!_inbuffer.empty())
{
_outbuffer += _handler(_inbuffer);
}
if (!_outbuffer.empty())
{
Sender();
// GetOwner()->EnableReadWrite(_sockfd, true, true);
}
}
void Sender() override
{
while (true)
{
ssize_t n = send(_sockfd, _outbuffer.c_str(), _outbuffer.size(), 0);
if(n > 0)
{
_outbuffer.erase(0, n);
if(_outbuffer.empty())
{
break;
}
}
else if(n == 0)
{
break;
}
else
{
if(errno == EAGAIN || errno == EWOULDBLOCK)
{
// 缓冲区写满
break;
}
if(errno == EINTR)
{
// 被信号中断
continue;
}
else
{
// 发送出错
Excepter(); // 异常处理
return;
}
}
}
if(!_outbuffer.empty())
{
// 1. 发送缓冲区写满(发送条件不具备)
// 开启对写事件的关心
GetOwner()->EnableReadWrite(_sockfd, true, true);
}
else
{
// 2. 数据发送完毕
// 关闭对写事件的关心
GetOwner()->EnableReadWrite(_sockfd, true, false);
}
}
int GetSockFd() override
{
return _sockfd;
}
void Excepter() override
{
// 处理所有的异常
// 移除文件描述符, 删除connection
GetOwner()->DelConnection(_sockfd);
}
std::string &Inbuffer()
{
return _inbuffer;
}
void AppendOutBuffer(const std::string &out)
{
_outbuffer += out;
}
~Channel()
{
}
private:
int _sockfd; // 该连接的文件描述符
std::string _inbuffer; // 输入缓冲区
std::string _outbuffer; // 输出缓冲区
InetAddr _client_addr; // 客户端信息
};
5.7.Protocol.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <jsoncpp/json/json.h>
#include <functional>
#include "Socket.hpp"
#include "Log.hpp"
// 自定义网络版本的计算器
using namespace SocketModule;
using namespace LogModule;
const std::string ProtSep = " ";
// client → server
class Request
{
public:
Request()
{
}
Request(int x, int y, char oper)
: _x(x), _y(y), _oper(oper)
{
}
// 序列化
std::string Serialize()
{
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
Json::FastWriter writer;
std::string s = writer.write(root);
return s;
}
// 反序列化
bool Deserialize(std::string &in)
{
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
}
return ok;
}
~Request()
{
}
int X()
{
return _x;
}
int Y()
{
return _y;
}
char Oper()
{
return _oper;
}
private:
int _x;
int _y;
char _oper;
};
// server → client
class Response
{
public:
Response()
{
}
Response(int result, int code)
: _result(result), _code(code)
{
}
// 序列化
std::string Serialize()
{
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
return writer.write(root);
}
// 反序列化
bool Deserialize(std::string &in)
{
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return ok;
}
~Response()
{
}
void SetResult(int res)
{
_result = res;
}
void SetCode(int code)
{
_code = code;
}
void ShowResult()
{
std::cout << "计算结果是: " << _result << "[ " << _code << " ]" << std::endl;
}
private:
int _result; // 运算结果
int _code; // 0: success 1,2,3,4: 不同异常
};
const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>; // 计算业务回调
class Protocol
{
public:
Protocol()
{
}
Protocol(func_t func)
: _func(func)
{
}
// 编码
std::string Encode(const std::string jsonstr)
{
std::string len = std::to_string(jsonstr.size());
return len + sep + jsonstr + sep;
}
// 解码
bool Decode(std::string &buffer, std::string *package)
{
ssize_t pos = buffer.find(sep);
if (pos == std::string::npos)
{
return false;
}
// 获取报文长度
std::string package_len_str = buffer.substr(0, pos);
int package_len_int = std::stoi(package_len_str);
// 计算一个完整报文长度
int target_len = package_len_str.size() + sep.size() + package_len_int + sep.size();
// 保证buffer里至少有一个完整报文
if (buffer.size() < target_len)
{
return false;
}
// 提取一个完整报文
*package = buffer.substr(pos + sep.size(), package_len_int);
// 将报文移除缓冲区
buffer.erase(0, target_len);
return true;
}
std::string Execute(std::string &package)
{
Request req;
bool ok = req.Deserialize(package);
if (!ok)
{
return std::string();
}
// 完成计算功能
Response resp = _func(req);
// 序列化
std::string json_str = resp.Serialize();
// 添加自定义长度
std::string send_str = Encode(json_str);
// 返回运算结果
return send_str;
}
bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
{
while (true)
{
int n = client->Recv(&resp_buff);
if (n > 0)
{
// 解析报文
std::string json_package;
while (Decode(resp_buff, &json_package))
{
// 反序列化
resp->Deserialize(json_package);
}
return true;
}
else if (n == 0)
{
std::cout << "server quit" << std::endl;
return false;
}
else
{
std::cout << "recv error" << std::endl;
return false;
}
}
}
std::string BuildRequestString(int x, int y, char oper)
{
// 构建一个完整的请求
Request req(x, y, oper);
// 序列化
std::string json_req = req.Serialize();
// 添加自定义长度
return Encode(json_req);
}
~Protocol()
{
}
private:
func_t _func; // 计算业务回调
};
5.8.NetCal.hpp
cpp
#pragma once
#include "Protocol.hpp"
#include <iostream>
class Cal
{
public:
Response Execute(Request &req)
{
Response resp(0, 0);
switch (req.Oper())
{
case '+':
resp.SetResult(req.X() + req.Y());
break;
case '-':
resp.SetResult(req.X() - req.Y());
break;
case '*':
resp.SetResult(req.X() * req.Y());
break;
case '/':
{
if (req.Y() == 0)
{
resp.SetCode(1); // 1: 除0错误
}
else
{
resp.SetResult(req.X() / req.Y());
}
}
break;
case '%':
{
if (req.Y() == 0)
{
resp.SetCode(2); // 2: 模0错误
}
else
{
resp.SetResult(req.X() % req.Y());
}
}
break;
default:
resp.SetCode(3); // 3: 非法操作
break;
}
return resp;
}
};
5.9.Main.cc
cpp
#include <iostream>
#include <string>
#include "Reactor.hpp"
#include "Listener.hpp"
#include "Channel.hpp"
#include "Log.hpp"
#include "Common.hpp"
#include "Protocol.hpp"
#include "NetCal.hpp"
static void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
// ./server port
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
LogModule::ConsoleLogStrategy();
uint16_t port = std::stoi(argv[1]);
// 构建业务对象
std::shared_ptr<Cal> cal = std::make_shared<Cal>();
// 构建Protocol对象
std::shared_ptr<Protocol> protocol = std::make_shared<Protocol>(
[&cal](Request &req) -> Response
{
return cal->Execute(req);
});
// 构建Listener对象
std::shared_ptr<Connection> conn = std::make_shared<Listener>(port);
conn->RegisterHandler(
[&protocol](std::string &inbuffer) -> std::string
{
std::string response_str;
while (true)
{
std::string package;
if (!protocol->Decode(inbuffer, &package))
{
break;
}
response_str += protocol->Execute(package);
}
return response_str;
});
// 构建Reactor对象
std::unique_ptr<Reactor> R = std::make_unique<Reactor>();
R->AddConnection(conn);
R->Loop();
return 0;
}
5.10.TcpClient.cc
cpp
#include "Socket.hpp"
#include "Common.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <string>
#include <memory>
using namespace SocketModule;
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}
// 获取数据
void GetDataFromStdin(int *x, int *y, char *oper)
{
std::cout << "Please Enter x: ";
std::cin >> *x;
std::cout << "Please Enter y: ";
std::cin >> *y;
std::cout << "Please Enter oper: ";
std::cin >> *oper;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();
client->BuildTcpClientSocketMethod();
if (client->Connect(server_ip, server_port) != 0)
{
// 连接失败
std::cerr << "connect error" << std::endl;
exit(CONNECT_ERR);
}
std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
std::string resp_buffer;
// 服务器连接成功
while (true)
{
// 获取数据
int x, y;
char oper;
GetDataFromStdin(&x, &y, &oper);
// 构建请求
std::string req_str = protocol->BuildRequestString(x, y, oper);
// 发送请求
client->Send(req_str);
// 获取应答
Response resp;
bool res = protocol->GetResponse(client, resp_buffer, &resp);
if (res == false)
{
break;
}
// 显示结果
resp.ShowResult();
}
client->Close();
return 0;
}
5.11.Socket.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
namespace SocketModule
{
using namespace LogModule;
const static int gbacklog = 16;
// 设计模式: 模板方法模式
class Socket
{
public:
virtual ~Socket() {} // 析构函数
virtual void SocketOrDie() = 0; // 创建套接字
virtual void BindOrDie(uint16_t port) = 0; // 绑定IP和端口号
virtual void ListenOrDie(int backlog) = 0; // 监听
virtual int Accept(InetAddr *client) = 0; // 获取
virtual void Close() = 0; // 关闭
virtual int Recv(std::string *out) = 0; // 读取
virtual int Send(const std::string &message) = 0; // 写入
virtual int Connect(const std::string &server_ip, uint16_t port) = 0; // 连接
virtual int Fd() = 0;
public:
void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
void BuildTcpClientSocketMethod()
{
SocketOrDie();
}
};
const static int defaultfd = -1;
class TcpSocket : public Socket
{
public:
TcpSocket()
: _sockfd(defaultfd)
{
}
TcpSocket(int fd)
: _sockfd(fd)
{
}
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
int opt = 1;
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
LOG(LogLevel::INFO) << "socket success: " << _sockfd;
}
void BindOrDie(uint16_t port) override
{
InetAddr localaddr(port);
int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
void ListenOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
int opt = 1;
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 设置地址复用
LOG(LogLevel::INFO) << "listen success";
}
#define ACCEPT_ERR -3
#define ACCEPT_CONTINUE -2
#define ACCEPT_DONE -1
int Accept(InetAddr *client) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd = ::accept(_sockfd, CONV(peer), &len);
if (fd < 0)
{
// LOG(LogLevel::WARNING) << "accept warning...";
// return -1;
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
return ACCEPT_DONE; // 底层没有新连接
}
else if (errno == EINTR)
{
return ACCEPT_CONTINUE; // 被信号中断, 继续读取
}
else
{
LOG(LogLevel::WARNING) << "accept error";
return ACCEPT_ERR; // 读取出错
}
}
return fd;
}
void Close() override
{
if (_sockfd >= 0)
{
::close(_sockfd);
}
}
int Recv(std::string *out) override
{
// 流式读取
char buffer[4096];
ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
*out += buffer;
}
return n;
}
int Send(const std::string &message) override
{
return ::send(_sockfd, message.c_str(), message.size(), 0);
}
int Connect(const std::string &server_ip, uint16_t port) override
{
InetAddr server(server_ip, port);
return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
}
int Fd() override
{
return _sockfd;
}
~TcpSocket()
{
}
private:
int _sockfd;
};
};
5.12.Log.hpp
cpp
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <string>
#include <filesystem> // C++17中 文件操作的相关封装
#include <fstream>
#include "Mutex.hpp"
#include <memory>
#include <unistd.h>
#include <sstream>
#include <ctime>
#include <cstdio>
namespace LogModule
{
using namespace MutexModule;
const std::string gsep = "\r\n";
// 2. 刷新策略(策略模式: C++多态)
// 策略基类
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 策略a: 显示器打印
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy()
{
}
void SyncLog(const std::string &message) override
{
// 加锁
LockGuard lockguard(_mutex);
// 打印日志
std::cout << message << gsep;
}
~ConsoleLogStrategy()
{
}
private:
Mutex _mutex;
};
// 缺省参数
const std::string defaultpath = "/var/log/";
const std::string defaultfile = "my.log";
// 策略b: 指定文件写入
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
: _path(path), _file(file)
{
// 加锁
LockGuard lockguard(_mutex);
// 如果当前路径存在
if (std::filesystem::exists(_path))
{
return;
}
// 如果当前路径不存在
try
{
std::filesystem::create_directories(_path);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\n";
}
}
void SyncLog(const std::string &message) override
{
// 加锁
LockGuard lockguard(_mutex);
// "./log" + "/" + "my.log"
std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
// 以追加的方式打开文件
std::ofstream out(filename, std::ios::app);
if (!out.is_open())
{
return;
}
// 写入日志
out << message << gsep;
// 关闭文件
out.close();
}
~FileLogStrategy()
{
}
private:
std::string _path; // 日志文件所在路径
std::string _file; // 日志文件名称
Mutex _mutex; // 互斥锁
};
// 形成完整日志 && 根据策略选择不同刷新方式
// 1. 形成日志等级
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string LeveltoStr(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "UNKNOW";
}
}
// 2. 获取时间方法
std::string GetTimeStamp()
{
time_t curr = time(nullptr);
struct tm curr_tm;
localtime_r(&curr, &curr_tm);
char timebuffer[128];
snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d-%02d-%02d",
curr_tm.tm_year + 1900, curr_tm.tm_mon + 1, curr_tm.tm_mday,
curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec);
return timebuffer;
}
// 日志类
class Logger
{
public:
Logger()
{
// 默认使用显示器
EnableConsoleLogStrategy();
}
void EnableFileLogStrategy()
{
_fflush_strategy = std::make_unique<FileLogStrategy>();
}
void EnableConsoleLogStrategy()
{
_fflush_strategy = std::make_unique<ConsoleLogStrategy>();
}
// 内部类: 表示未来的一条日志
class LogMessage
{
public:
LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
: _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger)
{
// 日志左半部分
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << LeveltoStr(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _src_name << "] "
<< "[" << _line_number << "] "
<< "- ";
_loginfo = ss.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
// 日志右半部分
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._fflush_strategy)
{
_logger._fflush_strategy->SyncLog(_loginfo);
}
}
private:
std::string _curr_time; // 时间
LogLevel _level; // 等级
pid_t _pid; // 进程PID
std::string _src_name; // 文件名
int _line_number; // 行号
std::string _loginfo; // 一条完整的日志信息
Logger &_logger;
};
LogMessage operator()(LogLevel level, std::string name, int line)
{
return LogMessage(level, name, line, *this);
}
~Logger()
{
}
private:
std::unique_ptr<LogStrategy> _fflush_strategy;
};
// 全局日志对象
Logger logger;
// 使用宏简化用户操作, 获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enbale_File_Log_Strategy() logger.EnableFileLogStrategy()
};
#endif
5.13.InetAddr.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
// 网络地址 <=> 主机地址
class InetAddr
{
public:
InetAddr()
{
}
InetAddr(struct sockaddr_in &addr)
{
SetAddr(addr);
}
InetAddr(const std::string &ip, uint16_t port)
: _ip(ip), _port(port)
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
// 主机序列 → 网络序列(端口号)
_addr.sin_port = htons(_port);
// 点分十进制 → 网络序列(IP地址)
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
}
InetAddr(uint16_t port)
: _port(port), _ip("0")
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
_addr.sin_addr.s_addr = INADDR_ANY;
}
void SetAddr(struct sockaddr_in &addr)
{
_addr = addr;
// 网络序列 → 主机序列(端口号)
_port = ntohs(_addr.sin_port);
// 网络序列 → 点分十进制(IP地址)
char ipbuffer[64];
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
_ip = ipbuffer;
}
uint16_t Port()
{
return _port;
}
std::string Ip()
{
return _ip;
}
const struct sockaddr_in &NetAddr()
{
return _addr;
}
const struct sockaddr *NetAddrPtr()
{
return CONV(_addr);
}
socklen_t NetAddrLen()
{
return sizeof(_addr);
}
bool operator==(const InetAddr &addr)
{
return addr._ip == _ip && addr._port == _port;
}
std::string StringAddr()
{
return _ip + " : " + std::to_string(_port);
}
~InetAddr()
{
}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
5.14.Common.hpp
cpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
enum ExitCode
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
FORK_ERR,
OPEN_ERR,
EPOLL_CREATE_ERR,
EPOLL_CTL_ERR
};
// 禁止拷贝
class NoCopy
{
public:
NoCopy()
{
}
~NoCopy()
{
}
NoCopy(const NoCopy &) = delete;
const NoCopy &operator=(const NoCopy &) = delete;
};
int defaultport = 8080;
void SetNonBlock(int fd)
{
int fl = fcntl(fd, F_GETFL);
if(fl < 0)
{
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
#define CONV(addr) ((struct sockaddr *)&addr)
5.15.Makefile
cpp
server:Main.cc
g++ -o $@ $^ -std=c++17 -l jsoncpp
.PHONY:clean
clean:
rm -f server
六、OTOL设计模式
6.1.核心概念
One Thread One Loop:描述基于事件驱动编程的架构
一个执行流,一个Reactor
每一个线程运行一个独立的事件循环,从而实现高效的并发处理
6.2.多进程实现方案

6.3.多线程实现方案
方案一:

方案二:
