linux网络编程(三)select、poll和epoll
一、为什么会有多路I/O转接服务器?
为什么会有多路I/O转接服务器呢?在学这个之前,我们同使用的是多线程或者多进程的方式来连接服务器,那么cpu将会占用大量资源来处理。那么我们试想一下,如果有几千台客户端来连接呢,那么就要创建几千个进程或者线程。有没有一种不怎么占用cpu资源的方式呢。这时候多路I/O转接服务器的这种机制就很符合这种情况。操作系统会用内核来监听文件,而不再用进程或者线程来监听,相当于请了一个“帮手”,当这些客户端请求的时候,这个“帮手”会帮你处理好,再反馈给程序,此时调用accept就无需等待了,内核已经帮你弄好了
二、select
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds: 监控有读数据到达文件描述符集合,传入传出参数
writefds: 监控写数据到达文件描述符集合,传入传出参数
exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout: 定时阻塞监控时间,3种情况
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "wrap.h"
#define SERV_PORT 6666
#define CONNECT 30
int main()
{
int listen_fd,ret,maxfd, nready,con_fd,i,sockfd,n;
int client[FD_SETSIZE];
char buf[BUFSIZ] = { 0 };
//创建套接字,已经封装好错误处理
listen_fd = Socket(AF_INET,SOCK_STREAM,0);
//绑定套接字
struct sockaddr_in sockadd;
struct sockaddr_in clie_add;
socklen_t len;
len = sizeof(struct sockaddr_in);
bzero(&sockadd,sizeof(struct sockaddr_in));
bzero(&clie_add, sizeof(struct sockaddr_in));
sockadd.sin_family = AF_INET;
sockadd.sin_port = htons(SERV_PORT);
sockadd.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listen_fd,(struct sockaddr*)&sockadd,sizeof(struct sockaddr_in));
//设置同时3次握手的次数
Listen(listen_fd, CONNECT);
//初始化最大数量的文件描述符
maxfd = listen_fd;
fd_set rset;
fd_set allset;
FD_SET(listen_fd,&allset);
FD_SET(listen_fd, &rset);
int maxi = -1; // client[]的下标
//初始化
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* 用-1初始化client[] */
while (1)
{
rset = allset;
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if (FD_ISSET(listen_fd, &rset)) //判断是否是新的客户端连接
{
printf("new client online......\n");
con_fd=Accept(listen_fd, (struct sockaddr*)&clie_add,&len);
FD_SET(con_fd, &allset);
for (i = 0; i < FD_SETSIZE; i++)
{
if (client[i] < 0)
{
client[i] = con_fd;
break;
}
}
if (i > maxi)
{
maxi = i;
}
//最大值更新
if (con_fd > maxfd)
{
maxfd = con_fd;
}
if (--nready == 0)
continue;
}
else //读数据
{
for (i = 0; i <= maxi; i++)
{
if ((sockfd = client[i]) < 0)
{
continue;
}
if (FD_ISSET(sockfd, &rset))
{
//读数据
if ((n = Read(sockfd, buf, sizeof(buf))) == 0)
{
Close(sockfd); /* 当client关闭链接时,服务器端也关闭对应链接 */
FD_CLR(sockfd, &allset); /* 解除select监控此文件描述符 */
client[i] = -1;
}
else
{
int j;
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Write(sockfd, buf, n);
}
if (--nready == 0)
{
break;
}
}
}
}
}
return 0;
}
三、poll
poll是select进阶的函数,
int poll(struct pollfd fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; / 文件描述符 /
short events; / 监控的事件 /
short revents; / 监控事件中满足条件返回的事件 */
};
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "wrap.h"
#include <poll.h>
#define SERV_PORT 6666
#define CONNECT 30
#define OPEN_MAX 1024
int main()
{
int listen_fd, ret, maxfd, nready, con_fd, i, sockfd, n;
struct pollfd client[OPEN_MAX];
char buf[BUFSIZ] = { 0 };
//创建套接字,已经封装好错误处理
listen_fd = Socket(AF_INET, SOCK_STREAM, 0);
//绑定套接字
struct sockaddr_in sockadd;
struct sockaddr_in clie_add;
socklen_t len;
len = sizeof(struct sockaddr_in);
bzero(&sockadd, sizeof(struct sockaddr_in));
bzero(&clie_add, sizeof(struct sockaddr_in));
sockadd.sin_family = AF_INET;
sockadd.sin_port = htons(SERV_PORT);
sockadd.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listen_fd, (struct sockaddr*)&sockadd, sizeof(struct sockaddr_in));
//设置同时3次握手的次数
Listen(listen_fd, CONNECT);
//初始化最大数量的文件描述符
maxfd = listen_fd;
fd_set rset;
fd_set allset;
FD_SET(listen_fd, &allset);
FD_SET(listen_fd, &rset);
int maxi = 0; // client[]的下标
//初始化
for (i = 0; i < OPEN_MAX; i++)
client[i].fd = -1; /* 用-1初始化client[] */
while (1)
{
nready = poll(client, maxi+1,-1);
if (client[0].revents & POLLIN) //判断是否是新的客户端连接
{
printf("new client online......\n");
con_fd = Accept(listen_fd, (struct sockaddr*)&clie_add, &len);
FD_SET(con_fd, &allset);
for (i = 1; i < FD_SETSIZE; i++)
{
if (client[i].fd < 0)
{
client[i].fd = con_fd;
break;
}
}
client[i].events = POLLIN;
if (i > maxi)
{
maxi = i;
}
//最大值更新
if (con_fd > maxfd)
{
maxfd = con_fd;
}
if (--nready == 0)
continue;
}
else //读数据
{
for (i = 1; i <= maxi; i++)
{
if ((sockfd = client[i].fd) < 0)
{
continue;
}
if (client[i].revents & POLLIN)
{
//读数据
if ((n = Read(sockfd, buf, sizeof(buf))) < 0)
{
if (errno = ECONNRESET) //收到RST标志
{
printf("client[%d] aborted connection\n",i);
Close(sockfd);
client[i].fd = -1;//不监听该文件描述符
}
}
if ((n = Read(sockfd, buf, sizeof(buf))) == 0)
{
Close(sockfd); /* 当client关闭链接时,服务器端也关闭对应链接 */
FD_CLR(sockfd, &allset); /* 解除select监控此文件描述符 */
client[i].fd = -1;
}
else
{
int j;
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Write(sockfd, buf, n);
}
if (--nready == 0)
{
break;
}
}
}
}
}
return 0;
}
三、epoll
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd: 为epoll_creat的句柄
op: 表示动作,用3个宏来表示:
EPOLL_CTL_ADD (注册新的fd到epfd),
EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
EPOLL_CTL_DEL (从epfd删除一个fd);
event: 告诉内核需要监听的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR: 表示对应的文件描述符发生错误
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "wrap.h"
#include <poll.h>
#include <sys/epoll.h>
#define SERV_PORT 6666
#define CONNECT 30
#define OPEN_MAX 1024
int main()
{
int listen_fd, ret, maxfd, nready, con_fd, i, sockfd, n, efd,res;
struct epoll_event tep, ep[OPEN_MAX];
struct pollfd client[OPEN_MAX];
char buf[BUFSIZ] = { 0 };
char str[BUFSIZ] = { 0 };
//创建套接字,已经封装好错误处理
listen_fd = Socket(AF_INET, SOCK_STREAM, 0);
//绑定套接字
struct sockaddr_in sockadd;
struct sockaddr_in clie_add;
socklen_t len;
len = sizeof(struct sockaddr_in);
bzero(&sockadd, sizeof(struct sockaddr_in));
bzero(&clie_add, sizeof(struct sockaddr_in));
sockadd.sin_family = AF_INET;
sockadd.sin_port = htons(SERV_PORT);
sockadd.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listen_fd, (struct sockaddr*)&sockadd, sizeof(struct sockaddr_in));
//设置同时3次握手的次数
Listen(listen_fd, CONNECT);
//初始化最大数量的文件描述符
maxfd = listen_fd;
fd_set rset;
fd_set allset;
FD_SET(listen_fd, &allset);
FD_SET(listen_fd, &rset);
int maxi = 0; // client[]的下标
efd = epoll_create(OPEN_MAX);
if (efd == -1)
{
perror("epoll error:");
exit(1);
}
tep.events = EPOLLIN;
tep.data.fd = listen_fd;
res = epoll_ctl(efd,EPOLL_CTL_ADD,listen_fd,&tep);
if (res == -1)
{
perror("epoll_ctl error:");
exit(1);
}
while (1)
{
nready = epoll_wait(efd,ep,OPEN_MAX,-1);
if (nready == -1)
{
perror("epoll_wait error:");
exit(1);
}
for (i = 0; i < nready; i++)
{
if (!(ep[i].events & EPOLLIN))
{
continue;
}
if (ep[i].data.fd == listen_fd)
{
len = sizeof(clie_add);
con_fd = Accept(listen_fd, (struct sockaddr*)&clie_add, &len);
printf("received from %s at PORT %d \n",
inet_ntop(AF_INET, &clie_add.sin_addr.s_addr, str, sizeof(BUFSIZ)),
ntohs(clie_add.sin_port));
tep.events = EPOLLIN;
tep.data.fd = con_fd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, con_fd, &tep);
if (res == -1)
{
perror("epoll_ctl error:");
exit(1);
}
}
else
{
sockfd = ep[i].data.fd;
n = Read(sockfd, buf, BUFSIZ);
if (n == 0)
{
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
if (res == -1)
{
perror("epoll_ctl error:");
exit(1);
}
close(sockfd);
printf("client[%d] closed connection\n", sockfd);
}
else if (n < 0)
{
perror("read n<0 error:");
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
close(sockfd);
}
else
{
for (i = 0; i < n; i++)
{
buf[i] = toupper(buf[i]);
}
Write(STDOUT_FILENO, buf, n);
Write(sockfd, buf, n);
}
}
}
}
return 0;
}