1.select的缺点:
1.select监听的文件描述符集合是一个数组,受 fd_set 的大小限制,有上限(1024个)
2.select监听的文件描述符集合在应用层,内核层监听事件后需要传递给用户层带来资源开销
3.select需要用户手动查找产生事件的文件描述符
4.select只能工作在水平触发模式(低速模式)而无法工作在边沿触发模式(高速模式)
2.poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout); 功能: 监听文件描述符集合,工作方式类似于select 参数: fds:文件描述符集合首地址 nfds:文件描述符集合的数组的长度 timeout:超时时间,单位毫秒,-1表示永久等待 返回值: 成功返回产生事件文件描述符个数 失败返回-1 超时仍然没有产生的事件返回0 struct pollfd { int fd; /* file descriptor */ short events; 要监听的事件 short revents; 不用赋值,有事件产生,操作系统会返回 }; |
相当于用户告诉内核,你要帮我关心哪个文件描述符上的什么事件,内核会通过revents告诉用户,该文件描述符上的事件已经就绪
poll 函数采用链表的方式替代原来 select 中 fd_set 结构,因此可监听文件描述符数量不受限。
缺点:
1.poll监听的文件描述符集合在应用层,内核层监听事件后需要传递给用户层带来资源开销
2.poll需要用户手动查找产生事件的文件描述符
3.poll只能工作在水平触发模式(低速模式)而无法工作在边沿触发模式(高速模式)
步骤:先初始化文件描述符集(不初始化的话,不知道文件描述符是否可用),遍历文件描述符集,如果事件已经就绪,做相应的操作
#include "head.h"
#define MAX_POLL_FDNUM 10000
int CreateListenSocket(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int confd)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
sprintf(tmpbuff, "%s --- echo", tmpbuff);
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
return nsize;
}
//初始化所有文件描述符值为-1
int InitFds(struct pollfd *pfds, int maxnum)
{
int i = 0;
for (i = 0; i < maxnum; i++)
{
pfds[i].fd = -1;
}
return 0;
}
//添加新文件描述符到文件描述符集合中
int AddFd(struct pollfd *pfds, int maxlen, int newfd, short env)
{
int i = 0;
for (i = 0; i < maxlen; i++)
{
if (-1 == pfds[i].fd)
{
pfds[i].fd = newfd;
pfds[i].events = env;
break;
}
}
return 0;
}
//从文件描述符集合中删除文件描述符
int DelFd(struct pollfd *pfds, int maxlen, int tmpfd)
{
int i = 0;
for (i = 0; i < maxlen; i++)
{
if (pfds[i].fd == tmpfd)
{
pfds[i].fd = -1;
break;
}
}
return 0;
}
int main(void)
{
int sockfd = 0;
int confd = 0;
int ret = 0;
struct pollfd fds[MAX_POLL_FDNUM];
int nready = 0;
int i = 0;
sockfd = CreateListenSocket(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("创建监听套接字失败\n");
return -1;
}
InitFds(fds, MAX_POLL_FDNUM);
AddFd(fds, MAX_POLL_FDNUM, sockfd, POLLIN);
while (1)
{
nready = poll(fds, MAX_POLL_FDNUM, -1);
if (-1 == nready)
{
perror("fail to poll");
return -1;
}
for (i = 0; i < MAX_POLL_FDNUM; i++)
{
if (-1 == fds[i].fd)
{
continue;
}
if ((fds[i].revents & fds[i].events) && (fds[i].fd == sockfd))
{
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
printf("处理连接失败\n");
DelFd(fds, MAX_POLL_FDNUM, sockfd);
close(sockfd);
continue;
}
AddFd(fds, MAX_POLL_FDNUM, confd, POLLIN);
}
else if ((fds[i].revents & fds[i].events) && (fds[i].fd != sockfd))
{
ret = HandleConnection(fds[i].fd);
if (-1 == ret)
{
printf("接收异常!\n");
close(fds[i].fd);
DelFd(fds, MAX_POLL_FDNUM, fds[i].fd);
continue;
}
else if (0 == ret)
{
printf("关闭连接!\n");
close(fds[i].fd);
DelFd(fds, MAX_POLL_FDNUM, fds[i].fd);
continue;
}
}
}
}
return 0;
}
3.epoll
epoll的优点:
1.epoll没有文件描述符上限限制
2.epoll监听的事件表在内核层,内核监听事件不需要操作用户层空间提高效率
3.epoll会获得产生事件的文件描述符,不需要用户查找
4.epoll可以工作在边沿触发模式(高速模式),提高效率
高速模式:事件处理通知,只通知一次
低速模式:事件未处理,一直通知
函数接口:
1. 在内核层创建一张epoll监听的事件表(epoll_create)
int epoll_create(int size); 功能: 在内核层创建一张epoll监听的事件表 参数: size:监听的事件表大小 返回值: 成功返回新的文件描述符 失败返回-1 |
2.管理内核中epoll事件表(epoll_ctl )
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); typedef union epoll_data { /* events: EPOLLIN:文件描述符有数据可读时触发 EPOLLOUT:文件描述符有数据可写时触发 EPOLLET:边沿触发模式 */ |
3. 监听epfd对应的事件表中是否有事件发生 (epoll_wait)
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); |
#include "head.h"
#define MAX_POLL_FDNUM 10000
int CreateListenSocket(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = INADDR_ANY;
ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int confd)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
sprintf(tmpbuff, "%s --- echo", tmpbuff);
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
return nsize;
}
int AddFd(int epfd, int fd, uint32_t tmpenvent)
{
struct epoll_event env;
int ret = 0;
env.events = tmpenvent;
env.data.fd = fd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &env);
if (-1 == ret)
{
perror("fail to epoll_ctl");
return -1;
}
return ret;
}
int DelFd(int epfd, int fd)
{
int ret = 0;
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
if (-1 == ret)
{
perror("fail to epoll_ctl");
return -1;
}
return ret;
}
int main(void)
{
int sockfd = 0;
int confd = 0;
int ret = 0;
int epfd = 0;
int nready = 0;
int i = 0;
struct epoll_event retenv[MAX_POLL_FDNUM];
sockfd = CreateListenSocket(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("创建监听套接字失败\n");
return -1;
}
//在内核层创建一张epoll监听的事件表
epfd = epoll_create(MAX_POLL_FDNUM);
if (-1 == epfd)
{
perror("fail to epoll_create");
return -1;
}
//在epoll事件表中添加sockfd
ret = AddFd(epfd, sockfd, EPOLLIN);
if (-1 == ret)
{
printf("添加套接字失败\n");
return -1;
}
while (1)
{ //监听epfd对应的事件表中是否有事件发生
nready = epoll_wait(epfd, retenv, MAX_POLL_FDNUM, -1);
if (-1 == nready)
{
perror("fail to epoll_wait");
return -1;
}
//处理所有发生事件
for (i = 0; i < nready; i++)
{
if (retenv[i].data.fd == sockfd)
{
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
printf("处理连接失败\n");
DelFd(epfd, sockfd);
close(sockfd);
continue;
}
AddFd(epfd, confd, EPOLLIN);
}
else if (retenv[i].data.fd != sockfd)
{
ret = HandleConnection(retenv[i].data.fd);
if (-1 == ret)
{
printf("接收异常!\n");
DelFd(epfd, retenv[i].data.fd);
close(retenv[i].data.fd);
continue;
}
else if (0 == ret)
{
printf("关闭连接!\n");
DelFd(epfd, retenv[i].data.fd);
close(retenv[i].data.fd);
continue;
}
}
}
}
close(epfd);
return 0;
}
注意:select,poll,epoll都是单任务处理的,一个事件一个事件的处理,后面的只能等前面的处理完才能处理,类似排队。