一个典型的select的工作流程:
//定义 文件描述符集合,一个是read, 一个是write
fd_set fd_in, fd_out;
struct timeval tv;
// 情况集合信息
FD_ZERO( &fd_in );
FD_ZERO( &fd_out );
// 将sock1加入到读监控集合中
FD_SET( sock1, &fd_in );
// 将sock2加入到写监控集合中
FD_SET( sock2, &fd_out );
//找出最大的socket值,在select监控时要用
int largest_sock = sock1 > sock2 ? sock1 : sock2;
//select超时时间为10s
tv.tv_sec = 10;
tv.tv_usec = 0;
// Call the select
int ret = select( largest_sock + 1, &fd_in, &fd_out, NULL, &tv );
// Check if select actually succeed
if ( ret == -1 )
// select出错
else if ( ret == 0 )
// select在指定时间范围内,没有检测到就绪的文件描述符
else
{
if ( FD_ISSET( sock1, &fd_in ) )
// sock1上读就绪
if ( FD_ISSET( sock2, &fd_out ) )
// sock2上写就绪
示例代码
流程图:[ref] https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_72/rzab6/xnonblock.htm
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> /* port we're listening on */ #define PORT 10000 int main(int argc, char *argv[]) { /* master file descriptor list */ fd_set master; /* temp file descriptor list for select() */ fd_set read_fds; /* server address */ struct sockaddr_in serveraddr; /* client address */ struct sockaddr_in clientaddr; /* maximum file descriptor number */ int fdmax; /* listening socket descriptor */ int listener; /* newly accept()ed socket descriptor */ int newfd; /* buffer for client data */ char buf[10]; int nbytes; /* for setsockopt() SO_REUSEADDR, below */ int yes = 1; int addrlen; int i, j; /* clear the master and temp sets */ FD_ZERO(&master); FD_ZERO(&read_fds); /* get the listener */ if((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("Server-socket() error lol!"); /*just exit lol!*/ exit(1); } printf("Server-socket() is OK...\n"); /*"address already in use" error message */ if(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("Server-setsockopt() error lol!"); exit(1); } printf("Server-setsockopt() is OK...\n"); /* bind */ serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = INADDR_ANY; serveraddr.sin_port = htons(PORT); memset(&(serveraddr.sin_zero), '\0', 8); if(bind(listener, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) { perror("Server-bind() error lol!"); exit(1); } printf("Server-bind() is OK...\n"); /* listen */ if(listen(listener, 10) == -1) { perror("Server-listen() error lol!"); exit(1); } printf("Server-listen() is OK...\n"); /* add the listener to the master set */ FD_SET(listener, &master); /* keep track of the biggest file descriptor */ fdmax = listener; /* so far, it's this one*/ /* loop */ for(;;) { /* copy it */ read_fds = master; if(select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) { perror("Server-select() error lol!"); exit(1); } //printf("Server-select() is OK...\n"); /*run through the existing connections looking for data to be read*/ for(i = 0; i <= fdmax; i++) { if(FD_ISSET(i, &read_fds)) { /* we got one... */ if(i == listener) { /* handle new connections */ addrlen = sizeof(clientaddr); if((newfd = accept(listener, (struct sockaddr *)&clientaddr, &addrlen)) == -1) { perror("Server-accept() error lol!"); } else { printf("Server-accept() is OK...\n"); FD_SET(newfd, &master); /* add to master set */ if(newfd > fdmax) { /* keep track of the maximum */ fdmax = newfd; } printf("%s: New connection from %s on socket %d\n", argv[0], inet_ntoa(clientaddr.sin_addr), newfd); } } else { printf("recv data:%s\n",buf); /* handle data from a client */ if((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0) { /* got error or connection closed by client */ if(nbytes == 0) { /* connection closed */ printf("%s: socket %d hung up\n", argv[0], i); /* close it... */ close(i); /* remove from master set */ FD_CLR(i, &master); } else perror("recv() error lol!"); } else { printf("send back info to %d\n", i); if(send(i, buf, nbytes, 0) == -1) perror("send() error lol!"); } } } } } return 0; }Select方式的问题:
1. 支持的文件描述符集合有限,一般为1024个;
2. 每次都要遍历所有的文件描述符,使用FD_ISSET来判断就绪信息,如果集合较大时,就绪的很少时,会造成遍历浪费;
3.每次调用select,都要将fd的集合从用户态拷贝进入内核态,同时,要在内核态遍历所有的fd;
后续提出的poll方式,只是解决了select的支持上线问题,后面两个问题还是存在的。
因此,又提出了epoll方式。
在内核版本2.5.45之后,提供了epoll接口。
Epoll的工作方式在本质上和poll是一样的,但是做了很大的优化:
1)Fd数组是被保存在内核的,而不是在用户态;
2)可以通过系统调用创建一个集合,从集合中添加,删除fd,也可以从集合中获取事件信息。
这种方式比poll性能提升了很多,因为和poll相比,避免了每次调用poll接口时,在内核态和用户态对fd集合的双重扫描。
Epoll的接口集合如下:
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_create()函数
用来创建一个poll集合,参数size只是一个指示器,不代表fd的数量,该函数的返回值是一个fd,用来标识poll集合的(fd集合),出错则返回-1.一个epoll的fd可以通过close()来关闭,否则就像一个IO fd一样。
Epoll_ctl被用来添加,移除,或者控制对一个fd的监控方式. op参数指定了操作方式:
EPOLL_CTL_ADD:
添加一个fd。参数event的结构形式:
uint32_t events:fd的事件位图,
(EPOLLIN, EPOLLPRI, EPOLLOUT, EPOLLRDNORM, EPOLLRDBAND, EPOLLWRNORM, EPOLLWRBAND, EPOLLMSG, EPOLLERR, and EPOLLHUP),
epoll_data_t data:一个共同体,可以被用来指定事件相关的附加信息。
void *ptr; //可以通过ptr携带fd信息,还有其他附加信息
int fd; //可以通过fd携带fd信息
uint32_t u32;
uint64_t u64;
EPOLL_CTL_MOD:对监控内容进行修改
EPOLL_CTL_DEL:删除对一个fd的监控
Epoll_wait()被用来从fd集合中读取事件信息,参数epfd标识epoll的集合,events是指向epoll_events数组的指针。
