多路IO转接服务器
学习目标:
掌握端口复用函数
了解半关闭及shutdown函数
掌握select实现多路IO转接
了解poll函数
掌握epoll实现多路IO转接
端口复用:
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t optlen);
参数
scokfd:标识一个套接口的描述字。
level:选项定义层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
optname:需设置的选项。
optval:指针,指向存放选项待设置的新值的缓冲区。
optlen:optval缓冲区的长度。
常用方法设置端口复用
int opt = 1; //设置端口复用
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,(void *)&opt,sizeof(opt));
半关闭:
通信双方中只有一方关闭
close(cfd);
shutdown(int fd,int how);
参数
scokfd:标识一个套接口的描述字。
how:SHUT_RD、SHUT_WR、SHUT_RDWR。
shutdown在关闭多个文件描述符应用的文件时,采用全关闭方法。close,只关闭一个。
select函数:
select函数: 该函数用于监视文件描述符的变化情况——读写或是异常。
int select(int maxfd + 1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
参数
maxfd + 1:最大文件描述符加1。
readset:用于检查可读性。
writeset:用于检查可写性。
exceptset:用于检查带外数据。
timeout:一个指向timeval结构的指针,用于决定select等待I/O的最长时间。
timeout:
> 0:设置监听超时时长。
NULL 阻塞监听。
0:非阻塞监听,轮询。
返回值
>0:就绪描述字的正数目。
-1:出错。
0:超时。
select优缺点:
- 优点:跨平台
- 缺点:
- 监听上限受文件描述符限制。最大1024
- 检测满足条件的fd,自己添加业务逻辑提高小。提高了编码难度。
poll函数:
poll函数: 把当前的文件指针挂到等待队列。
int poll(struct pollfd *fds,nfds_t nfds,int timeout);
参数
fds:监听的文件描述符【数组】。
nfds:事件有效监听数组的个数。
timeout:
> 0:超时时长,单位毫秒。
-1:阻塞等待
0:不阻塞
返回值
满足对应监听事件的文件描述符总个数。
poll优缺点:
- 优点:
- 自带数组结构,可以将监听事件集合和返回事件集合分离。
- 扩展监听上限,超时1024限制。
- 缺点:
- 不能跨平台
- 无法直接定位满足监听事件的文件描述符,编码难度较大。
epoll函数:
poll函数: 为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
int epoll_create(int size);//创建一个epoll的句柄。
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);//epoll事件注册函数,先注册要监听的事件类型。
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);//等待事件的产生
参数
size:创建的红黑树的监听节点数量(仅供内核参考)。
epfd:epoll_create函数的返回值。
op:对该监听红黑树所作的操作。
EPOLL_CTL_ADD:添加fd到监听红黑树。
EPOLL_CTL_MOD:修改fd在监听红黑树上的监听事件。
EPOLL_CTL_DEL:将一个fd从监听红黑树上摘下(取消监听)。
fd:待监听的fd。
event:本质struct epoll_event 结构体地址。
events:传出参数,满足监听条件的fd结构体。
maxevents:数组元素的总个数。1024
timeout:
> 0:超时时长,单位毫秒。
-1:阻塞等待
0:不阻塞
返回值
epoll_create:函数
成功:指向新创建的红黑树的根节点fd。
失败:-1 设置errno。
epoll_ctl:函数
成功:0。
失败:-1 设置errno。
epoll_wait:函数
> 0:满足监听的总个数,可用作循环上限。
-1:失败 设置errno。
0:没有fd满足监听事件。
struct epoll_event{
__uint32_t events;
epoll_adta_t data;
};
//events可以是一下即个宏的集合
//- EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
//- EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
//- EPOLLERR:表示对应的文件描述符发生错误;
//- EPOLLHUP:表示对应的文件描述符被挂断;
//- EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
//- EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
/***********************************/
//data:联合体
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
epoll事件模型:
- ET模式: 边沿触发,缓冲区剩余未读尽的数据不会导致epoll_wait返回,新的事件满足才会触发。(EPOLLET)
- LT模式: 水平触发(默认采用模式),缓冲区剩余未读尽的数据会导致epoll_wait返回。
结论: epoll的ET模式,高效模式,但只支持非阻塞模式(忙轮询)。
使用方法
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&event);
int flag = fcntl(cfd,F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd,F_SETFL,flag);
补充:
read函数返回值:
返回值
> 0:实际读到的字节数。
= 0:socket中,表示对端关闭。
-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
#写入
# softnofile 65536 设置默认值,可以直接借助命令修改【注销用户、使其生效】
# hard nofile 100000 命令修改上限。