Day4
一、poll函数使用
- 函数说明
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
函数说明: 跟select类似, 委托内核监控可读, 可写, 异常事件
函数参数:
fds: 一个struct pollfd结构体数组的首地址
struct pollfd {
int fd; //要监控的文件描述符,如果fd为-1, 表示内核不再监控
short events; //输入参数, 表示告诉内核要监控的事件, 读事件, 写事件, 异常事件
short revents;//输出参数, 表示内核告诉应用程序有哪些文件描述符有事件发生
};
events/revents:
POLLIN:可读事件
POLLOUT: 可写事件
POLLERR: 异常事件
nfds: 告诉内核监控的范围, 具体是: 数组下标的最大值+1
timeout:
=0: 不阻塞, 立刻返回
-1: 表示一直阻塞, 直到有事件发生
>0: 表示阻塞时长, 在时长范围内若有事件发生会立刻返回;
如果超过了时长也会立刻返回
函数返回值:
>0: 发生变化的文件描述符的个数
=0: 没有文件描述符发生变化
-1: 表示异常
- 开发流程
使用poll模型开发服务端流程:
{
1 创建socket, 得到监听文件描述符lfd----socket()
2 设置端口复用----setsockopt()
3 绑定----bind()
4 监听----listen()
5 struct pollfd client[1024];
client[0].fd = lfd;
client[0].events = POLLIN;
int maxi = 0;
for(i=1; i<1024; i++)
{
client[i].fd = -1;
}
while(1)
{
nready = poll(client, maxi+1, -1);
//异常情况
if(nready<0)
{
if(errno==EINTR) // 被信号中断
{
continue;
}
break;
}
//有客户端连接请求到来
if(client[0].revents==POLLIN)
{
//接受新的客户端连接
cfd = accept(lfd, NULL, NULL);
//寻找在client数组中可用位置
for(i=0; i<1024; i++)
{
if(client[i].fd==-1)
{
client[i].fd = cfd;
client[i].events = POLLIN;
break;
}
}
//客户端连接数达到最大值
if(i==1024)
{
close(cfd);
continue;
}
//修改client数组下标最大值
if(maxi<i)
{
maxi = i;
}
if(--nready==0)
{
continue;
}
}
//下面是有客户端发送数据的情况
for(i=1; i<=maxi; i++)
{
sockfd = client[i].fd;
//如果client数组中fd为-1, 表示已经不再让你内核监控了, 已经close了
if(client[i].fd==-1)
{
continue;
}
if(client[i].revents==POLLIN)
{
//read 数据
n = read(sockfd, buf, sizeof(buf));
if(n<=0)
{
close(sockfd);
client[i].fd = -1;
}
else
{
//发送数据给客户端
write(sockfd, buf, n);
}
if(--nready==0)
{
break;
}
}
}
}
close(lfd);
}
二、epoll函数
- 函数介绍
函数介绍:
int epoll_create(int size)
函数说明:创建一个epoll树根
参数说明:size表示最大节点数(大于0)
返回值:成功:返回一个大于0的文件描述符,代表整个epoll树的树根
失败:返回-1,并设置error值
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数说明:将要监听的节点在epoll树上添加, 删除和修改
参数说明:epfd: epoll树根
op:EPOLL_CTL_ADD: 添加事件节点到树上
EPOLL_CTL_DEL: 从树上删除事件节点
EPOLL_CTL_MOD: 修改树上对应的事件节点
fd: 事件节点对应的文件描述符
event: 要操作的事件节点
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
event.events常用的有:
EPOLLIN: 读事件
EPOLLOUT: 写事件
EPOLLERR: 错误事件
EPOLLET: 边缘触发模式
event.fd: 要监控的事件对应的文件描述符
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
函数说明:等待内核返回事件发生
参数说明:
epfd: epoll树根
events: 传出参数, 其实是一个事件结构体数组
maxevents: 数组大小
timeout:
-1: 表示永久阻塞
0: 立即返回
>0: 表示超时等待事件
返回值:
成功: 返回发生事件的个数
失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值,
epoll_wait的events是一个传出参数, 调用epoll_ctl传递给内核什么值, 当epoll_wait返回的时候, 内核就传回什么值,不会对struct event的结构体变量的值做任何修改.
开发思路
使用epoll模型开发服务器流程:
{
1 创建socket, 得到监听文件描述符lfd----socket()
2 设置端口复用----setsockopt()
3 绑定----bind()
4 监听----listen()
5 创建一棵epoll树
int epfd = epoll_create();
//将监听文件描述符上树
struct epoll_event ev;
ev.evetns = EPOLLIN; //可读事件
ev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
struct epoll_event events[1024];
while(1)
{
nready = epoll_wait(epfd, events, 1024, -1);
if(nready<0)
{
if(errno==EINTR)//被信号中断
{
continue;
}
break;
}
for(i=0; i<nready; i++)
{
sockfd = events[i].data.fd;
//有客户端连接请求到来
if(sockfd==lfd)
{
cfd = accept(lfd, NULL, NULL);
//将cfd对应的读事件上epoll树
ev.data.fd = cfd;
ev.evetns = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
continue;
}
//有客户端发送数据过来
n = Read(sockfd, buf, sizeof(buf));
if(n<=0)
{
close(sockfd);
//将sockfd对应的事件节点从epoll树上删除
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
perror("read error or client closed");
continue;
}
else
{
write(sockfd, buf, n);
}
}
}
Close(epfd);
close(lfd);
return 0;
}
- 代码实现
#include <stdio.h>
#include <ctype.h>
#include "wrap.h"
#include <sys/epoll.h>
int main()
{
int lfd;
int cfd;
int i;
int k;
char buf[1024];
int nready;
int n;
int sockfd;
//创建socket
lfd = Socket(AF_INET, SOCK_STREAM, 0);
//设置端口复用
int opt;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
//绑定
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd, (struct sockaddr *)&serv, sizeof(struct sockaddr_in));
//监听
Listen(lfd, 128);
//创建一棵epoll树
int epfd = epoll_create(1024);
if(epfd < 0)
{
perror("create epoll error");
return -1;
}
//将监听文件描述符lfd上树
struct epoll_event ev;
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
struct epoll_event events[1024];
while(1)
{
nready = epoll_wait(epfd, events, 1024, -1);
if(nready < 0)
{
if(errno == EINTR)
{
continue;
}
break;
}
for(i = 0; i < nready; i++)
{
sockfd = events[i].data.fd;
//有客户端连接请求到来
if(sockfd == lfd)
{
cfd = Accept(lfd, NULL, NULL);
//将cfd文件描述符进行上树操作
ev.data.fd = cfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
continue;
}
//有客户端数据发送
memset(buf, 0x00, sizeof(buf));
n = Read(sockfd, buf, sizeof(buf));
if(n<=0)
{
close(sockfd);
//将sockfd对应的文件描述符从epoll树上删除
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
perror("read error or client closed");
continue;
}
else
{
printf("n==[%d], buf==[%s]", n, buf);
for(k = 0; k < n; k++)
{
buf[k] = toupper(buf[k]);
}
Write(sockfd, buf, n);
}
}
}
Close(epfd);
close(lfd);
return 0;
}
epoll的LT和ET模式:
1 epoll默认情况下是LT模式, 在这种模式下, 若读数据一次性没有读完,缓冲区中还有可读数据, 则epoll_wait还会再次通知
2 若将epoll设置为ET模式, 若读数据的时候一次性没有读完, 则epoll_wait不再通知,直到下次有新的数据发来.