文章

IO多路复用

IO多路复用

引言

在上篇[“理解网络IO”]中实现的一请求一线程的方式优点是其代码逻辑简单,但缺点也是很明显的,不太利于并发,若每个线程8M,16G内存大概能做到1k的并发,无法做到更多。 故而需要用多路复用技术如select、poll、epoll。

select

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    fd_set rfds, rset;
    //将rfds集合清空
    FD_ZERO(&rfds);
    //将ID为sockfd的IO置1
    FD_SET(sockfd,&rfds); 
    int maxfd = sockfd;
    while(1){
        rset = rfds;
        //看有几个fd就绪
        int nready = select(maxfd+1, &rset, NULL, NULL, NULL);
		//首先看监听的fd是否就绪
        if(FD_ISSET(sockfd,&rset)){ // accept-->listenfd
            //创建客户端fd
            int clientfd = accept(sockfd,(struct sockaddr*)&clientaddr, &len);
	        printf("accept finished %d\n",clientfd);
            //将当前fd加入集合
            FD_SET(clientfd,&rfds);
            if(clientfd > maxfd) maxfd = clientfd;
        } 
        // recv
        //监听到有fd后遍历所有非监听fd来进行数据的接收
        int i = 0;
        for(i = sockfd+1; i <= maxfd; i++){
            if(FD_ISSET(i,&rset)){
                char buffer[1024] = {0};
                int count = recv(i,buffer,1024,0);
                if(count == 0){ //disconnect
                    printf("client disconnect %d\n",i);
                    close(i);
                    FD_CLR(i, &rfds);
                    continue; 
                }
                printf("RECV: %s\n",buffer);
                count = send(i,buffer,count,0);
            }
        }
    }

select核心是通过集合set来对IO进行管理,理论上每个IO只占用3个bit来表示所有信息,这极大地减少了内存的占用

poll

poll的多路复用是通过定义下面的pollfd结构体来代替set集合实现的一个pollfd代表一个IO。

1
2
3
4
5
struct pollfd {
    int   fd;         /* 要监视的文件描述符 */
    short events;     /* 要监视的事件(输入) */
    short revents;    /* 实际发生的事件(输出) */
};

完整代码如下,逻辑与select很像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
	//pollfd这里fds数组结构体跟select中的set集合是一个意思
    struct pollfd fds[1024] = {0}; 
    fds[sockfd].fd = sockfd;
    fds[sockfd].events = POLLIN; //关注读事件
    int maxfd = sockfd;
    while(1){
    	//参数较少
        int nready = poll(fds, maxfd+1, -1);
        if(fds[sockfd].revents & POLLIN){ //accept
            int clientfd = accept(sockfd,(struct sockaddr*)&clientaddr, &len);
            printf("accept finished %d\n", clientfd);
            fds[clientfd].fd = clientfd;
            fds[clientfd].events = POLLIN;
            if(clientfd > maxfd) maxfd = clientfd;
        }
        int i = 0;
        for(i = sockfd+1; i <= maxfd; i++){
            if(fds[i].revents & POLLIN){
                char buffer[1024] = {0};
                int count = recv(i,buffer,1024,0);
                if(count == 0){ //disconnect
                    printf("client disconnect %d\n",i);
                    close(i);
                    fds[i].fd = -1;
                    fds[i].events = POLLIN;
                    continue; 
                }
                printf("RECV: %s\n",buffer);
                count = send(i,buffer,count,0);
            }
        }
    }

epoll(重点)

背景:

在服务器端用的最多的系统是Linux,其最核心的原因就是有epoll支持。在Linux 2.4版本还没有epoll,server端没有人使用都是用的Unix或Windows,但在Linux 2.6之后引入了epoll,这使得server端能够对IO做到更多操作。

在这里插入图片描述

epoll的理解如上图所示,epoll的工作即为在一段固定时间去检查就绪fd里拿取fd来进行操作。

与select对比优势:

  1. 能做到100wIO。
  2. 100wIO是慢慢积累起来,来一个添加一个,且处理时只处理就绪IO就行了,而select需要集合一起放进函数。
  3. epoll创立了就绪集与整个集合,这两个分开能使我们更好地只关注需要处理的事件。

epoll需要掌握的主要有三个接口函数:

1
2
3
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中也有一个结构体叫epoll_event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    int epfd = epoll_create(1024);
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

    while(1){
        struct epoll_event events[1024] = {0};
        int nready = epoll_wait(epfd, events, 1024, -1);
        int i = 0;
        for(i = 0; i < nready; i++){
            int connfd = events[i].data.fd;

            if(connfd == sockfd){
                int clientfd = accept(sockfd,(struct sockaddr*)&clientaddr, &len);
                printf("accept finished %d\n", clientfd);

                ev.events = EPOLLIN;
                ev.data.fd = clientfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
            } else {
                char buffer[1024] = {0};
                
                int count = recv(connfd,buffer,1024,0);
                if(count == 0){ //disconnect
                    printf("client disconnect %d\n",connfd);
                    close(connfd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
                    continue; 
                }
                printf("RECV: %s\n",buffer);
                count = send(connfd,buffer,count,0);
            }
        }
    }
本文由作者按照 CC BY 4.0 进行授权

热门标签