poll
了解poll函数
介于select和epoll之间,用得少,只是简单了解
int poll(struct pllfd* fds, nfds_t nfds,int timeout)
函数说明:跟select相似,监控多路IO但是不能跨平台,委托内核监控可读可写,以及监控异常事件
参数选择:
fds:传入传出参数,实际上是一个结构体数组
{
int fds.fd:要监控的文件描述符
short fds.event://要监控的事件:读,写,异常
POLLIN->读事件
POLLOUT->写事件
short fds.revents:返回的事件,告诉用户哪些文件描述符发生变化
}
nfds:数组实际的有效个数,内核监控的范围,具体最大值+1
timeout:超时时间,单位毫秒
-1:永阻塞,直到事件发生
0:立刻返回
>0:直到监控时间发生或者超时
返回值:
》0:发生变化的文件描述符的个数
=0:没有文件描述符发生变化
=-1:异常发生
开发流程
建立socket
设置端口复用
绑定
定义结构体
{
//定义结构体数组
struct pollfd clients[1024]
clients[0].fd=lfd;//lfd是新建的socket
int maxi=0;
clients[0].events=POLLIN;
//初始化
for(i=1;i<1024;i++){
clients[i]=-1;
}
//委托内核监控
while(1)
{
int nready=poll(client,maxi+1,-1);
if(nready<0){
if(errno=EINTER){
continue;
}
break;
}
else
{
//有客户端连接请求到来
if(client[0].revents==POLLIN&&client[0].fd==lfd)
{
cfd=accept(lfd,NULL,NULL);
//寻找一个可用位置
for(int i=0;i<1024;i++)
{
if(clients[i].fd==-1)
{
clients[i].fd=cfd;
clients[i].events=POLLIN;
break;
}
if(i==1024){
//拒绝服务
close(cfd);
continue;
}
}
if(maxi<i){
maxi=i;//修改数组下标最大值
}
if(--nready==0)
{
continue;
}
}
//有客户端发送数据
if(client[i])
for(i=0;i<=maxi;i++){
if(client[i].fd==-1){
continue//中有文件描述符不再被内核监控,的直接从头跑
}
//有数据发过来
if(client[i].revents==POLLIN){
n=read(client[i].fd,buf,sizeof(buf));
if(n<=0)
{
close(client[i].fd);
client[i].revents=-1;/不再监控了
continue;
}
else{
write(client[i].fd,buf,n)
}
}
}
}
}
close(lfd)
}
说明:返回的时候,events和fd不会变化,输出使revents,输入和输出分离,请求和返回分离
如果fd成员赋值为-1,poll不会监控
poll相对于select没有本质上的变化,但是可以突破1024的限制
epoll
函数说明
头文件:#include <sys/epoll.h>
创建
int epoll_create(int size);
函数说明:创建一棵epoll树,返回一个树根节点,就是一个文件描述符,标识树根
参数:被忽略了,传一个大于0的就行,实际是表明组曾epoll数的节点个数,但是实际会不断扩充
返回值:返回一个文件描述符,标识树的树根
增删改
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数说明:能够将一个文件描述符对应的epoll结点进行更改,比如上树或者下树或者修改对应的事件(读或者写)
参数:
int epfd,树根结点
int op, 标识修改的方式,表名我们想做怎样的操作
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
int fd, 目标文件描述符
struct epoll_event *event
struct epoll_event {
__uint32_t events; /* Epoll events */
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
epoll_data_t data; /* User data variable */要操作的文件描述符
//这个字段是一个联合体,联合体在同一时间只有一个成员变量,联合体占用的内存大小为所有成员中占用字节最大的成员决定,在此处,就是类型为uint_64_t 的u64。
//这里只会用到fd。
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
};
委托内核监控
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
函数说明:委托内核监控的事件结点,返回的数组中的事件结点的只读不改,
是当时上epoll树的时候设置的值,其实就两种结点,一种监听结点,一种通信结点
参数:
int epfd, //树根结点
struct epoll_event * events,//传出参数,结构体数组
int maxevents, //数组大小
int timeout//监控时间,-1标识一直阻塞,0不阻塞,》0标识阻塞时长
epoll开发流程
{
1.创建socket
2.设置端口复用,
3.绑定
4.监听
5.int epfd=epoll-creat(1)//创建树根
6·上树
struct epoll_event ev;
ev.events=EPOLLIN;//表示对应的文件描述符可以读
ev.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
7.进入while循环等待事件发生
while(1)
{
struct epoll_event events[1024];
nready=epoll(epfd,events,1024,-1)
if(nready<0)
{
if(errno=EINTER)
{
continue;
}
break;
}
8。有事件发生,并且知道多少个事件发生变化
for(i=0;i<nready;i++)
{
//有两类事件,连接请求或者数据发送
sockfd=events[i].data.fd;
//判断是否是链接
if(sockfd==lfd)
{
cfd=accept(lfd,NULL,NULL);//得到新的文件描述符、
//新的文件描述符也要上树,读事件
ev.data.fd=cfd;
ev.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
}
else//有数据发过来
{
n=read(sockfd,buf,sizeof(buf));
if(n<=0)//关闭链接或者读异常
{
//关闭连接
close(so0ckfd);
//下树,下数的时候最后一个参数可以设置为为NULL
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
continue;
}
else
{
write(sockfd,buf,n);
}
}
}
}
close(lfd);
}
触发模式
ET:edge triger
每次到来数据就触发一次,就算没读完也不会再通知了
LT:level triger
只要缓冲区中有数据就一直通知,默认情况下是水平触发,没读完就会一直读一直读直到读到最后的回车符(包括回车符)
如果想改为边缘触发模式,那么只需要在通信文件描述符cfd的上树事件改为
ev.events=EPOLLIN | EPOLLET;
思考
在ET模式下如一次读完数据
答:循环读,单数如果没有数据你还要读,读到的不是0,而是-1,而且会在read那里阻塞,那么会在read那里阻塞,这样就需要对cfd文件描述符设置非阻塞
在通信文件描述符上树之后,加入以下代码
int flag=fcntl(cfd,F_GETFL,0);
flag |=O_NONBLOCK;
fcntl(cfd,F_SETFL,flag);
同时对读到的加入判断,是否等于-1单独拎出来说
if(n==-1)
{
break;//不需要关闭链接,只是读完了数据并不是异常
}
if(n==0||(n<0&&n!=-1))//真的出现了异常而且异常不是读取完毕,或者是对方关闭了链接(0)
{
//关闭连接
close(so0ckfd);
//下树,下数的时候最后一个参数可以设置为为NULL
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
break;
}
反应堆
一个for循环,里面没运行一次就更改函数指针指向的地址,有点多态那味儿了~