引言
poll函数介绍
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
(man poll 调用)
函数说明: 跟select类似, 委托内核监控可读, 可写, 异常事件
函数参数:
fds: 一个struct pollfd结构体数组的首地址
events/revents:
nfds: 告诉内核监控的范围, 具体是: 数组下标的最大值+1
timeout:
函数返回值:
poll函数开发流程
1 创建socket ,得到监听文件描述符,lfd ----- socket();
2 设置端口复用----------setsockopt()
3 绑定 ------ bind()
4
struct pollfd client[1024];
client[0].fd = lfd; // 放在哪都行,放在最俩头方便使用
client[0].events = POLLIN; //监控读事件,如果也让其监控可写事件,用或
// 设置为fd 为-1 ,表示内核不在监控,这是一个初始化
int maxi = 0; // 定义最大数组下标
for(int i = 0;i < 1024;i ++)
{
client[i].fd = -1;
}
//委托内核持续监控
k= 0;
while(1)
{
nready = poll(client,maxi + 1,-1);
//异常情况
if(nready < 0 )
{
if(error == EINTR)
{
continue;
}
break;
}
if(client[0].revents = POLLIN)
{
//接受新的客户端连接
k ++;
cfd = Accept(lfd,NULL,NULL);
/*继续委托内核监听事件
寻找在client 数组中可用位置*/
for(i = 0;i < 1024;i ++ )
{
if(client[i ].fd ==-1 )
{
client.fd[i] = cfd;
client.fd[i] = POLLIN;
break;
}
}
//客户端连接数达到最大值
if(i == 1024)
{
close(cfd);
continue; //退出,可能会有客户端连接退出,方便继续寻找
}
//修改client 数组下标最大值
if(maxi < i )
maxi = i;
if(--nready == 0 )
continue;
}
//下面是有客户端发送数据的情况
for(i = 1;i <= maxi;i ++)
{
//如果client数组中fd 为-1,表示已经不再让内核监控了
if(client[i].fd == -1)
continue;
if(client[i].revents == POLLIN)
{
sockfd = client[i].fd;
memset(buf,0x00,sizeof(buf));
//read 数据
n = Read(sockfd, buf,sizeof(buf));
if(n <= 0)
{
printf("read error or client closed,n =[%d]\n",n);
close(sockfd);
client[i].fd = -1; //告诉内核不再监控
}
else
{
printf("read error,n == [%d],buf==[%s]\n,"n,buf);
//发送数据给客户端
Write(sockfd,buf,n);
}
if(--nready == 0 )
{
break;
}
}
}
close(lfd);
}
多路IO-epoll (重点)
将检测文件描述符的变化委托给内核去处理, 然后内核将发生变化的文件描述符对应的
事件返回给应用程序.
头文件
#include <sys/epoll.h>
函数
使用epoll 模型开发服务器流程
开发完整的代码
//EPOLL 模型测试
#include "wrap.h"
#include <sys/epoll.h>
#include <ctype.h>
int main()
{
int ret;
int n;
int nready;
int lfd;
int cfd;
int sockfd;
char buf[1024];
socklen_t socklen;
struct sockaddr_in svraddr;
struct epoll_event ev;
struct epoll_event events[1024];
int k;
int i;
//创建socket
lfd = Socket(AF_INET,SOCK_STREAM,0);
//设置文件描述符为端口复用
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
//绑定
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
svraddr.sin_port = htons(8888);
Bind(lfd,(struct sockaddr *)&svraddr,sizeof(struct sockaddr_in));
//Listen
Listen(lfd,128);
//创建一棵epoll树
int epfd = epoll_create(1024);
if(epfd < 0 )
{
perror("create epoll error");
return -1;
}
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev); //lfd 对应的事件节点上树
while(1)
{
nready = epoll_wait(epfd,events,1024,-1); //等待内核返回事件
if(nready < 0)
{
perror("epoll_wait error");
if(nready == EINTR) //判断是否收到了中断信号
{
continue;
}
break;
}
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 {
memset(buf,0x00,sizeof(buf));
n = Read(sockfd,buf,sizeof(buf));
if(n <= 0)
{
close(sockfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); //把sockfd从epfd树上删除
}
else
{
for(k = 0;k < n;k ++)
{
buf[k] = toupper(buf[k]); //返回大写
}
Write(sockfd,buf,n);
}
}
}
}
close(epfd);
close(lfd);
return 0;
}
epoll 的两种模式 ET 和 LT 模式
epoll 的LT模式:
epoll 的ET模式:
用ET模式下,为了防止第二个客户端可以正常连接,并且发送数据,需要将socket设置为非阻塞模式
ET设置了非阻塞模式是因为使用了边缘触发模式(EPOLLET)。在边缘触发模式下,当有数据可读时,只会触发一次EPOLLIN事件,如果该次读取没有将缓冲区中的数据全部读取完毕,下次还是会触发EPOLLIN事件。因此,为了保证每次读取完整的数据,需要将socket设置为非阻塞模式,避免在缓冲区没有全部读取完毕时进行阻塞。
代码:
//EPOLL 模型测试 ET
#include "wrap.h"
#include <sys/epoll.h>
#include <ctype.h>
#include <fcntl.h>
int main()
{
int ret;
int n;
int nready;
int lfd;
int cfd;
int sockfd;
char buf[1024];
socklen_t socklen;
struct sockaddr_in svraddr;
struct epoll_event ev;
struct epoll_event events[1024];
int k;
int i;
//创建socket
lfd = Socket(AF_INET,SOCK_STREAM,0);
//设置文件描述符为端口复用
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
//绑定
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
svraddr.sin_port = htons(8888);
Bind(lfd,(struct sockaddr *)&svraddr,sizeof(struct sockaddr_in));
//Listen
Listen(lfd,128);
//创建一棵epoll树
int epfd = epoll_create(1024);
if(epfd < 0 )
{
perror("create epoll error");
return -1;
}
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev); //lfd 对应的事件节点上树
while(1)
{
nready = epoll_wait(epfd,events,1024,-1); //等待内核返回事件
if(nready < 0)
{
perror("epoll_wait error");
if(nready == EINTR) //判断是否收到了中断信号
{
continue;
}
break;
}
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 | EPOLLET; //
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
//将cfd设置为非阻塞模式
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK; //O_NONBLOCK(非阻塞)标志位置为1。
fcntl(cfd, F_SETFL, flag);
}
//有客户端发送数据过来
else {
memset(buf,0x00,sizeof(buf));
while(1)
{
n = Read(sockfd,buf,sizeof(buf));
printf("n == [%d]\n",n);
if(n == -1)
{
printf("read over,n == [%d]\n",n);
break;
}
if(n < 0 || (n <0 && n!=-1)) //对方关闭连接,或者异常的情况
{
printf("n == [%d],buf == [%s]\n",n,buf);
close(sockfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); //把sockfd从epfd树上删除
break;
}
else
{
printf("n == [%d],buf == [%s]\n",n,buf);
for(k = 0;k < n;k ++)
{
buf[k] = toupper(buf[k]); //返回大写
}
Write(sockfd,buf,n);
}
}
}
}
}
close(epfd);
close(lfd);
return 0;
}