多路IO转换:
I/O多路复用使得程序能同时监听多个文件描述符能够提高程序的性能
原理:借助内核,select来监听,客户端连接、数据通信事件。
select原型:
int select (int maxfd + 1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval * timeout);
参数一:最大的文件描述符加1。
参数二:用于检查可读性,
参数三:用于检查可写性,
参数四:用于检查带外数据,
参数五:一个指向timeval结构的指针,用于决定select等待I/o的最长时间。如果为空将一直等待。t
select:
void FD_ZERO(fd_set*set); ---清空一个文件描述符集合。
fd_set rset;
FD_ZERO(&rset);
void FD_SET(int fd, fd_set *set); ---将一个文件描述符从监听集合 移除。
FD_CLR(4, &rset);
int FD_ISSET(int fd,fd_set *set); ---判断一个文件描述符是否在监听集合中。
返回值:在:1;不在:0;
FD_ISSET(4, &rset);
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds:监听的所有文件描述符中,最大文件描述符+1
readfds:读文件描述符监听集合。 传入、传出参数
writefds:写文件描述符监听集合。 传入、传出参数 NULL
exceptfds:异常 文件描述符监听集合 传入、传出参数 NULL
timeout: > 0: 设置监听超时时长。
NULL:阻塞监听
0: 非阻塞监听,轮询
返回值:
>0:所有监听集合(3个)中,满足对应事件的总数。
0: 没有满足监听条件的文件描述符
-1: errno
思路分析:
lfd=socket(); 创建套接字
bind(); 绑定地址结构
listen(); 设置监听上限
fd_set rset, allset; 创建r监听集合
FD_ZERO(&allset); 将r监听集合清空
FD_SET(lfd,&allset); 将lfd添加至读集合中。
while(1){
rset=allset; 保存监听集合
ret=select(lfd+1,&rset, NULL,NULL,NULL); 监听文件描述符集合对应事件。
if(ret>0){ 有监听的描述符满足对应事件
if(FD_ISSET(lfd,&rset)) { //1在。0不在
cfd=accept(); 建立连接,返回用于通信的文件描述符
maxfd=cfd;
FD_SET(cfd,&allset); 添加到监听通信描述符集合中。
}
for(i=lfd+1;i<=最大文件描述符;i++){
FD_ISSET(i,&rset) 有read、write事件
read()
小--大
write();
}
}
}
select优缺点:
缺点:监听上限受文件描述符限制。最大 1024
检测满足条件的fd,自己添加业务逻辑提高小。提高了编码难度。
优点:跨平台。win、Linux、macOS、Unix、mips
poll
poll:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:监听的文件描述符【数组】
struct pollfd{
int fd: 待监听的文件描述符
short events: 待监听的文件描述符对应的监听事件
取值:POLLIN、POLLOUT、POLLERR
short revnets: 传入时,给0。如果满足对应事件的话,返回 非0---》POLLIN、POLLOUT、POLLERR
}
nfds:监听数组的,实际有效监听个数。
timeout:> 0:超时时长。 单位:毫秒。
-1:阻塞等待
0:不阻塞
返回值:返回满足对应监听事件的文件描述符 总个数。
优点:
自带数组结构。将监听事件集合 和返回事件集合 分离。
拓展 监听上限。 超出1024限制。
缺点:
不能跨平台。Linux
无法直接定位满足监听事件的文件描述符, 编码难度较大。
read函数
read函数返回值:
> 0:实际读到的字节数
= 0:socket中,表示对端关闭。close()
-1:如果errno==EINTR 被异常终端。需要重启。
如果errno==EAGIN或EWOULDBLOCK以非阻塞方式读数据,但是没有数据。需要,再次读。
如果errno==ECONNRESET 说明连接被 重置。需要close( ),移除监听队列。
错误。
突破1024文件描述符限制:
cat /proc/sys/fs/file-max -->当前计算机能打开的最大文件个数。受硬件影响。
ulimit -a ——>当前用户下的进程,默认打开文件描述符个数。缺省为1024
修改:
打开sudo vi /etc/security/limits.conf,写入:
* soft nofile 65536 ——>设置默认值,可以直接借助命令修改。【注销用户,使其生效】
* hard nofile 100000 ——>命令修改上限。
epoll
epoll:
int epoll_create(int size); 创建一棵监听红黑树
size:创建的红黑树的监听节点数量。(仅供内核参考。)
返回值:指向新创建的红黑树的根节点的fd。
失败:-1 errno
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 操作监听红黑树
epfd:epoll_create 函数的返回值。 epfd
op:对该监听红黑树所做的操作。
EPOLL_CTL_ADD 添加fd到 监听红黑树
EPOLL_CTL_MOD 修改fd在监听红黑树上的监听事件。
EPOLL_CTL_DEL 将一个fd从监听红黑树上摘下(取消监听)
fd:
待监听的fd
event:本质 struct epoll_event 结构体 地址
成员 events:
EPOLLIN/EPOLLOUT/EPOLLERR
成员 data:联合体:
int fd; 对应监听事件的fd
void *ptr;
uint32_t u32;
uint64_t u64;
返回值:成功 0;失败:-1 errno
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); 阻塞监听
epfd:epoll_create 函数的返回值。 epfd
events:传出参数,【数组】,满足监听条件的哪些fd结构体。
maxevents:数组 元素的总个数。 1024
struct epoll_event evnets[1024];
timeout:
-1:阻塞
0: 不阻塞
>0:超出时间(毫秒)
返回值:
>0:满足监听的总个数。 可以用作循环上限。
0:没有fd满足监听事件
-1:失败。 errno
epoll实现多路IO转接思路
lfd=socket(); 监听连接事件lfd
bind();
listen();
int epfd=epoll_create(1024); epfd,监听红黑树的树根。
struct epoll_event tep, ep[1024]; tep,用来设置单个fd属性,ep是epoll_wait()传出的满足监听事件的数组。
tep.events=EPOLLIN;
tep.data.fd=lfd
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&tep); 将lfd添加到监听红黑树上。
while(1){
ret=epoll_wait(epfd,ep,1024,-1); 实施监听
for(i=0;i<ret;i++){
if(ep[i].data.fd==lfd){ //lfd满足读事件,有新的客户端发起连接请求
cfd=Accept(); 初始化 cfd的监听属性
tep.data.fd=cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd, &tep);
}else{ cfd们满足读事件,有客户端写数据来。
n=read(ep[i].data.fd,buf,sizeof(buf));
if(n==0){
close(ep[i].data.fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,ep[i].data.fd,NULL); //将关闭的cfd,从监听树上摘下。
}else if(n>0){
小--大
write(ep[i].data.fd,buf,n)
}
}
}
}