什么是IO?
就拿 read
系统调用来说,从缓冲区中读取数据;首先要保证缓冲区有数据,若没有,操作就会被阻塞,也就是等待资源就绪;若有,将数据拷贝完之后直接返回;所以,IO分为两部分:等待资源就绪+拷贝数据;其中等待资源就绪在整个IO过程中占比非常大,如何降低等待的占比,也就是需要学习的目的
举个栗子:钓鱼的故事
张三,一个非常固执的钓鱼佬,在钓鱼的时候,只盯着鱼竿,身边发生什么都不在乎;
李四,一个随心所欲的钓鱼佬,在等待鱼上钩的时候,做着其他的事情,是不是地观察一个鱼竿,有没有鱼上钩;
王五,一个“非常懒”的钓鱼佬,把鱼竿放好,在上面安装一个报警器,一个有鱼上钩,直接把杆;
赵六,一个“多金”的钓鱼佬,每次都拿上十只鱼竿来钓鱼,没有都忙的不亦乐乎;
田七,一个爱吃鱼不爱钓鱼的有钱人,雇了一个小王,让他去钓鱼;
这五个人的钓鱼方式可以类比为五种IO方式:
张三-阻塞式IO,两耳不闻窗外事,一心只观水中浮
李四-非阻塞IO,闲来没事看两眼
王五-信号驱动IO,报警之后,立刻提杆
赵六-多路转接/多路复用
田七-异步IO
在整个钓鱼的过程中,鱼就是数据,河就是内核空间,鱼漂表明事件就绪,鱼竿就是文件描述符,钓鱼的动作也就是系统调用操作
钓鱼的人,等的占比越低,单位时间,钓鱼的效率就越高
多路转接/多路复用是高级IO的原因就是等待资源就绪的占比低
同步和异步关注的是消息通信机制
多进程多线程中, 也提到同步和互斥. 这里的同步通信和进程之间的同步是完全不想干的概念
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态
一个文件描述符, 默认都是阻塞IO
int fcntl(int fd, int cmd, ... /* arg */ );
fcntl函数有5种功能
此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞
基于fcntl, 我们实现一个SetNoBlock函数, 将文件描述符设置为非阻塞
void SetNonBlock(int fd)
{
int fl=fcntl(fd,F_GETFL);
if(fl<0)
{
std::cerr<<"fcntl: "<<strerror(errno)<<std::endl;
return ;
}
fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}
void SetNonBlock(int fd)
{
int fl = fcntl(fd, F_GETFL);
if (fl < 0)
{
std::cerr << "fcntl: " << strerror(errno) << std::endl;
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
int main()
{
SetNonBlock(0);
char buffer[1024];
while (true)
{
printf(">>>> ");
fflush(stdout);
ssize_t s = read(0, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s - 1] = 0;
std::cout << "echo# " << buffer << std::endl;
}
else if (s == 0)
{
std::cout << "read end" << std::endl;
break;
}
else
{
}
sleep(1);
}
}
结果与预期的一致,不过这里还存在一个问题,当s<0时,数据读取失败,打印结果又是怎么样的呢?
资源准备未就绪
系统提供select函数来实现多路复用输入/输出模型
select的函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
其实这个结构就是一个整数数组, 更严格的说, 是一个 “位图”. 使用位图中对应的位来表示要监视的文件描述符
提供了一组操作fd_set的接口, 来比较方便的操作位图
// 用来清除描述词组set中相关fd 的位
void FD_CLR(int fd, fd_set *set);
// 用来测试描述词组set中相关fd 的位是否为真
int FD_ISSET(int fd, fd_set *set);
// 用来设置描述词组set中相关fd的位
void FD_SET(int fd, fd_set *set);
// 用来清除描述词组set的全部位
void FD_ZERO(fd_set *set);
timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0
错误值可能是:
取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描
述符fd。则1字节长的fd_set最大可以对应8个fd;
作为输入时:表示用户告知内核,关心一下,集合中所有的fd事件;
比特位的位置,表示fd的数值;比特位的内容,表示是否关心
作为输出时:内核告知用户,所关心的多个fd中,有哪些已经就绪;
比特位的位置,表示fd的数值;比特位的内容,表示fd对应的事件已经就绪
Tcpserver:只处理读取,只获取数据的server
首先,监听端口可以交付给select,监听端口的连接就绪事件,其实就是读事件就绪
err.hpp
enum{
USAGE_ERR=1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR
};
log.hpp
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char*to_levelstr(int level)
{
switch(level)
{
case DEBUG: return "DEBUG";
case NORMAL: return "NORMAL";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
case FATAL: return "FATAL";
default: return nullptr;
}
}
void logMessage(int level,const char* format,...)
{
#define NUM 1024
char logprefix[NUM];
snprintf(logprefix,sizeof(logprefix),"[%s][%ld][pid:%d]",
to_levelstr(level),(long int)time(nullptr),getpid());
char logcontent[NUM];
va_list arg;
va_start(arg,format);
vsnprintf(logcontent,sizeof(logcontent),format,arg);
std::cout<<logprefix<<logcontent<<std::endl;
}
Sock.hpp
class Sock
{
const static int backlog=30;
public:
//创建socket文件套接字对象
static int Socket()
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
logMessage(FATAL,"创建套接字文件对象失败!");
exit(SOCKET_ERR);
}
logMessage(NORMAL,"创建套接字文件对象成功: %d",sock);
//设置地址复用
int opt=1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));
return sock;
}
//绑定自己的网络信息
static void Bind(int sock,int port)
{
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
logMessage(FATAL,"绑定失败!");
exit(BIND_ERR);
}
logMessage(NORMAL,"绑定成功!");
}
//设置socket为监听状态
static void Listen(int sock)
{
if(listen(sock,backlog)<0)
{
logMessage(FATAL,"监听失败!");
exit(LISTEN_ERR);
}
logMessage(NORMAL,"监听成功!");
}
static int Accept(int listensock,std::string*clientip,uint16_t*clientport)
{
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int sock=accept(listensock,(struct sockaddr*)&peer,&len);
if(sock<0)
{
logMessage(ERROR,"接收失败!");
}
else
{
logMessage(NORMAL,"接收一个新连接,sock: %d",sock);
*clientip=inet_ntoa(peer.sin_addr);
*clientport=ntohs(peer.sin_port);
}
return sock;
}
};
SelectServer.hpp
namespace select_yjm
{
static const int defaultport=8082;
static const int fdnum=sizeof(fd_set)*8;
static const int defaultfd=-1;
using func_t =std::function<std::string(const std::string&)>;
class SelectServer
{
public:
SelectServer(func_t f,int port=defaultport)
:_func(f)
,_port(port)
,_listensock(-1)
,fdarray(nullptr)
{
}
~SelectServer()
{
if(_listensock<0) close(_listensock);
if(fdarray) delete[] fdarray;
}
void initServer()
{
_listensock=Sock::Socket();
Sock::Bind(_listensock,_port);
Sock::Listen(_listensock);
fdarray=new int[fdnum];
for(int i=0;i<fdnum;i++) fdarray[i]=defaultfd;
fdarray[0]=_listensock;
}
void Print()
{
std::cout<<"fd list: ";
for(int i=0;i<fdnum;i++)
{
if(fdarray[i]!=defaultfd) std::cout<<fdarray[i]<<" ";
}
std::cout<<std::endl;
}
void Accepter(int listensock)
{
logMessage(DEBUG,"Accepter in");
//此时,listen不会阻塞,已经就绪
std::string clientip;
uint16_t clinetport=0;
int sock=Sock::Accept(listensock,&clientip,&clinetport);
if(sock<0)
{
return ;
}
logMessage(NORMAL,"accept success [%s:%d]",clientip.c_str(),clinetport);
//此时不能直接读取,只有select有资格检测事件是否就绪
//将新的sock交给select,本质就是将sock添加到fdarray数组中即可
int i=0;
for(;i<fdnum;i++)
{
if(fdarray[i]!=defaultfd) continue;
else break;
}
if(i==fdnum)
{
logMessage(WARNING,"server id full,please wait!");
close(sock);
}
else
{
fdarray[i]=sock;
}
Print();
logMessage(DEBUG,"Accepter out");
}
void Recver(int sock,int pos)
{
logMessage(DEBUG,"in Recver");
//读取请求,并不会阻塞
char buffer[1024];
ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);
if(s>0)
{
buffer[s]=0;
logMessage(NORMAL,"client# %s",buffer);
}
else if(s==0)
{
close(sock);
fdarray[pos]=defaultfd;
logMessage(NORMAL,"client quit");
return;
}
else
{
close(sock);
fdarray[pos]=defaultfd;
logMessage(ERROR,"client quit: %s",strerror(errno));
return;
}
//处理响应
std::string response=_func(buffer);
//返回response
write(sock,response.c_str(),response.size());
logMessage(DEBUG,"out Recver");
}
//
void HandlerReadEvent(fd_set& rfds)
{
for(int i=0;i<fdnum;i++)
{
//过滤非法的fd
if(fdarray[i]==defaultfd) continue;
//正常的fd,不一定就绪,需要判断
//此时只有监听事件就绪
if(FD_ISSET(fdarray[i],&rfds)&&fdarray[i]==_listensock) Accepter(_listensock);
//获取新连接之后,进行读取
else if(FD_ISSET(fdarray[i],&rfds)) Recver(fdarray[i],i);
else {}
}
}
void start()
{
for(;;)
{
fd_set rfds;
FD_ZERO(&rfds);
int maxfd=fdarray[0];
for(int i=0;i<fdnum;i++)
{
if(fdarray[i]==defaultfd) continue;
//合法的fd全部添加到读文件描述符集中
FD_SET(fdarray[i],&rfds);
//更新所有fd中最大的fd
if(maxfd<fdarray[i]) maxfd=fdarray[i];
}
logMessage(NORMAL,"max fd is: %d",maxfd);
//使用select,需要程序员维护一个保存所有合法的fd的数组
int n=select(maxfd+1,&rfds,nullptr,nullptr,nullptr);
switch(n)
{
case 0:
logMessage(NORMAL,"timeout...");
break;
case -1:
logMessage(WARNING,"select error,code: %d,err string: %s",errno,strerror(errno));
break;
default:
//表明有事件就绪,目前只有监听事件就绪
logMessage(NORMAL,"have event ready!");
HandlerReadEvent(rfds);
break;
}
}
}
private:
int _port;
int _listensock;
int *fdarray;
func_t _func;
};
}
epoll 有3个相关的系统调用
int epoll_create(int size);
创建一个epoll的句柄或者说是创建一个epoll模型(下面介绍):用完之后, 必须调用close()关闭
用户告知内核需要关心哪个文件描述符上的什么事件
int epoll_ctl(int epfd, int op, int fd,
struct epoll_event *event);
epoll的事件注册函数
第二个参数的取值:
EPOLL_CTL_ADD
:注册新的fd到epfd中EPOLL_CTL_MOD
:修改已经注册的fd的监听事件EPOLL_CTL_DEL
:从epfd中删除一个fdstruct epoll_event结构如下:
events可以是以下几个宏的集合:
EPOLLIN
:表示对应的文件描述符可以读 (包括对端SOCKET正常关闭)EPOLLOUT
:表示对应的文件描述符可以写EPOLLPRI
:表示对应的文件描述符有紧急的数据可读EPOLLERR
:: 表示对应的文件描述符发生错误EPOLLHUP
:表示对应的文件描述符被挂断EPOLLET
:将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的EPOLLONESHOT
:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里内核告知用户,那些文件描述符上的什么事件已经就绪
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
收集在epoll监控的事件中已经发送的事件
struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
}
struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}
epoll的使用过程就是三部曲
namespace epoll_yjm
{
static const int defaultport = 8083;
static const int size = 128;
static const int defaultvalue = -1;
static const int defaultnum = 64;
using func_t = std::function<std::string(const std::string &)>;
class EpollServer
{
public:
EpollServer(func_t f, uint16_t port = defaultport, int num = defaultnum)
: _func(f), _num(num), _revs(nullptr), _port(port), _listensock(defaultport), _epfd(defaultvalue)
{
}
~EpollServer()
{
if (_listensock != defaultvalue)
close(_listensock);
if (_epfd != defaultvalue)
close(_epfd);
if (_revs)
delete[] _revs;
}
void initserver()
{
// 1.创建socket
_listensock = Sock::Socket();
Sock::Bind(_listensock, _port);
Sock::Listen(_listensock);
// 2.创建epoll模型
_epfd = epoll_create(size);
if (_epfd < 0)
{
logMessage(FATAL, "epoll create error: %s", strerror(errno));
exit(EPOLL_CREATE_ERR);
}
// 3.添加listen到epoll模型中
struct epoll_event ev;
ev.events = EPOLLIN;
// 当事件就绪,被重新捞上来时,需要知道哪一个fd就绪
ev.data.fd = _listensock;
epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, &ev);
// 4.申请就绪事件的空间
_revs = new struct epoll_event[_num];
logMessage(NORMAL, "init server success!");
}
void HandlerEvent(int readynum)
{
logMessage(DEBUG, "HandlerEvent in");
for (int i = 0; i < readynum; i++)
{
uint32_t events = _revs[i].events;
int sock = _revs[i].data.fd;
if (sock == _listensock && events & EPOLLIN)
{
// listensock读事件就绪,获取新连接
std::string clientip;
uint16_t clientport;
int fd = Sock::Accept(sock, &clientip, &clientport);
if (fd < 0)
{
logMessage(WARNING, "accept error");
continue;
}
// 获取fd成功,不可以直接读,数据可能还没就绪
// 将fd放入epoll等待就绪
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
}
else if (events & EPOLLIN)
{
// 普通事件就绪
char buffer[1024];
// 把本轮数据读完,不一定能够读取完整的请求
int n = recv(sock, buffer, sizeof(buffer), 0);
if (n > 0)
{
buffer[n] = 0;
logMessage(DEBUG, "client# %s", buffer);
std::string response = _func(buffer);
send(sock, response.c_str(), response.size(), 0);
}
else if (n == 0)
{
// 建议先将fd从epoll中移除,再关闭
epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
close(sock);
logMessage(NORMAL, "client quit");
}
else
{
// 建议先将fd从epoll中移除,再关闭
epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
close(sock);
logMessage(ERROR,"recv error,code: %d,errstring: %s",errno,strerror(errno));
}
}
else
{
}
}
logMessage(DEBUG,"HandlerEvent out");
}
void start()
{
int timeout = -1;
for (;;)
{
int n = epoll_wait(_epfd, _revs, _num, timeout);
switch (n)
{
case 0:
logMessage(NORMAL, "timeout ...");
break;
case -1:
logMessage(WARNING, "epoll_wait failed,code: %d,errstring: %s", errno, strerror(errno));
break;
default:
logMessage(NORMAL, "have event ready!");
HandlerEvent(n);
break;
}
}
}
private:
uint16_t _port;
int _listensock;
int _epfd;
struct epoll_event *_revs;
int _num;
func_t _func;
};
}
epoll有2种工作方式-水平触发(LT)和边缘触发(ET)
假如有这样一个例子
你网购了好几个快递,张三快递员先给你派发了几个快递,你并没有去拿,因为你知道张三肯定会一直通知你,直到把你所有的快递都给你为止,因此你一直在忙别的事,直到很晚采取拿快递;又过了几天,你又网购了几个快递,不过这次给你派发快递的小哥李四不同于张三,他打电话通过你,并告知你:如果你不抓紧拿快递,那么就再也不通知你,你没有办法只能一次性将所有的快递都拿走;如果你这次没有将所有的快递都拿走,当李四再次拿到需要给你派发的快递时,就会再次通知你一次
epoll默认状态下就是LT工作模式
如果我们在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志, epoll进入ET工作模式
epoll既可以支持LT, 也可以支持ET
LT是 epoll 的默认行为. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中就把所有的数据都处理完;ET的高效不仅仅体现在通知机制上,为了尽快让上层将数据取走,TCP可以给发送方提供一个更大的窗口大小,让对方更新出更大的滑动窗口,提高底层的数据发送效率
相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的
Reactor=IO+协议定制+业务处理,基于ET模式下的Reactor,可以处理所有的IO
处理几个小细节:
在epollserver接收到数据之后,并不能确定数据的完整性,因为ET保证了数据被读取完,而协议才能保证数据的完整性;因此在epollserver还需要自己的接收缓冲区以保证数据的完整性
在epollserver同样也需要发送缓冲区,服务器刚开始启动,或者很多情况下,发送事件一直都是就绪的,可以直接发送;如果用户一次并没有把所有数据发送完,还需要再次发送;同样文件描述符上的发送事件也要注册到epoll模型中,一般按需设置
接下来就编写代码
tcpserver.hpp
namespace tcpserver
{
class Connection;
class Tcpserver;
static const uint16_t defaultport = 8084;
static const int num = 64;
using func_t = std::function<void(Connection *)>;
class Connection
{
public:
Connection(int sock,Tcpserver*tsp)
:_sock(sock)
,_tsp(tsp)
{
}
void Register(func_t r,func_t s,func_t e)
{
_recver=r;
_sender=s;
_excepter=e;
}
~Connection()
{
}
void Close()
{
close(_sock);
}
public:
int _sock;
std::string _inbuffer; // 输入缓冲区
std::string _outbuffer; // 输出缓冲区
func_t _recver; // 从sock中读取
func_t _sender; // 向sock中写入
func_t _excepter; // 处理sockIO的异常事件
Tcpserver *_tsp; // 回指指针
uint64_t lasttime;
};
class Tcpserver
{
private:
void Recver(Connection *conn)
{
conn->lasttime = time(nullptr);
char buffer[1024];
while (true)
{
ssize_t s = recv(conn->_sock, buffer, sizeof(buffer) - 1, 0);
if (s > 0)
{
buffer[s] = 0;
conn->_inbuffer += buffer; // 将读到的数据放入缓冲区中
logMessage(DEBUG, "\n%s", conn->_inbuffer);
_service(conn);
}
else if (s == 0)
{
if (conn->_excepter)
{
conn->_excepter(conn);
return;
}
}
else
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
else if (errno == EINTR)
continue;
else
{
if (conn->_excepter)
{
conn->_excepter(conn);
return;
}
}
}
}
}
void Sender(Connection *conn)
{
conn->lasttime = time(nullptr);
while (true)
{
ssize_t s = send(conn->_sock, conn->_outbuffer.c_str(), conn->_outbuffer.size(), 0);
if (s > 0)
{
if (conn->_outbuffer.empty())
break;
else
conn->_outbuffer.erase(0, s);
}
else
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
else if (errno == EINTR)
continue;
else
{
if (conn->_excepter)
{
conn->_excepter(conn);
return;
}
}
}
}
//如果没有发送完毕,需要对对应的sock开启写事件的关心
//如果发送完毕,需要关闭对写事件的关心
if(!conn->_outbuffer.empty())
conn->_tsp->EnableReadWrite(conn,true,true);
else
conn->_tsp->EnableReadWrite(conn,true,false);
}
void Excepter(Connection*conn)
{
logMessage(DEBUG,"Excepter begin");
_epoller.Control(conn->_sock,0,EPOLL_CTL_DEL);
conn->Close();
_connections.erase(conn->_sock);
logMessage(DEBUG,"关闭%d 文件描述符的所有资源",conn->_sock);
delete conn;
}
void Accepter(Connection *conn)
{
for (;;)
{
std::string clientip;
uint16_t clientport;
int err;
int sock = _sock.Accept(&clientip, &clientport, &err);
if (sock > 0)
{
AddConnection(
sock, EPOLLIN | EPOLLET,
std::bind(&Tcpserver::Recver, this, std::placeholders::_1),
std::bind(&Tcpserver::Sender, this, std::placeholders::_1),
std::bind(&Tcpserver::Excepter, this, std::placeholders::_1));
logMessage(DEBUG, "get a new link,info: [%s:%d]", clientip.c_str(), clientport);
}
else
{
if (err == EAGAIN || err == EWOULDBLOCK)
break;
else if (err == EINTR)
continue;
else
break;
}
}
}
void AddConnection(int sock, uint32_t events, func_t recver, func_t sender, func_t excepter)
{
// 1.首先给该sock创建connection,并初始化,并添加到_connections中
// 将该sock设置为非阻塞
if (events & EPOLLET)
Util::SetNonBlock(sock);
Connection *conn = new Connection(sock, this);
// 2.给对应的sock设置对应的回调处理方法
conn->Register(recver, sender, excepter);
// 3.将sock与其要关心的事件注册到epoll中
bool r = _epoller.AddEvent(sock, events);
assert(r);
(void)r;
// 4.将kv添加到_connections中
_connections.insert(std::pair<int, Connection *>(sock, conn));
logMessage(DEBUG, "add new sock: %d in epoll and unordered_map", sock);
}
bool IsConnectionExists(int sock)
{
auto iter = _connections.find(sock);
return iter != _connections.end();
}
void Loop(int timeout)
{
// 获得已就绪事件
int n = _epoller.Wait(_revs, _num, timeout);
for (int i = 0; i < n; i++)
{
int sock = _revs[i].data.fd;
uint32_t events = _revs[i].events;
// 将所有异常问题,全部转化为读写问题
if (events & EPOLLERR)
events |= (EPOLLIN | EPOLLOUT);
if (events & EPOLLHUP)
events |= (EPOLLIN | EPOLLOUT);
// listen事件就绪
if ((events & EPOLLIN) && IsConnectionExists(sock) && _connections[sock]->_recver)
_connections[sock]->_recver(_connections[sock]);
if ((events & EPOLLOUT) && IsConnectionExists(sock) && _connections[sock]->_sender)
_connections[sock]->_sender(_connections[sock]);
}
}
public:
Tcpserver(func_t func, uint16_t port = defaultport)
: _service(func), _port(port), _revs(nullptr)
{
}
~Tcpserver()
{
_sock.Close();
_epoller.Close();
if (_revs == nullptr)
delete[] _revs;
}
void initserver()
{
// 1.创建socket
_sock.Socket();
_sock.Bind(_port);
_sock.Listen();
// 2.创建epoll模型
_epoller.Create();
// 先将listenock设置为非阻塞
// 3.将目前唯一一个sock,添加到epoll模型中
AddConnection(_sock.Fd(), EPOLLIN | EPOLLET,
std::bind(&Tcpserver::Accepter, this, std::placeholders::_1), nullptr, nullptr);
_revs = new struct epoll_event[num];
_num = num;
}
void EnableReadWrite(Connection *conn, bool readable, bool writeable)
{
uint32_t event = (readable ? EPOLLIN : 0) | (writeable ? EPOLLIN : 0) | EPOLLET;
_epoller.Control(conn->_sock, event, EPOLL_CTL_MOD);
}
// 事件派发
void Dispatcher()
{
int timeout = 1000;
while (true)
{
Loop(timeout);
}
}
private:
uint16_t _port;
Sock _sock;
Epoll _epoller;
std::unordered_map<int, Connection *> _connections;
struct epoll_event *_revs;
int _num;
func_t _service;
};
}