IO -- select方式-epoll方式

xiaoxiao2021-02-28  33

一个典型的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的事件位图,

(EPOLLINEPOLLPRIEPOLLOUTEPOLLRDNORMEPOLLRDBANDEPOLLWRNORMEPOLLWRBANDEPOLLMSGEPOLLERR, 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数组的指针。

转载请注明原文地址: https://www.6miu.com/read-2626118.html

最新回复(0)