Linux I/O复用 epoll

来源:互联网 发布:vb.net excel二次开发 编辑:程序博客网 时间:2024/05/31 19:11

http://www.cnblogs.com/zhangchaoyang/articles/2681893.html



首先看个结构体

复制代码
typedef union epoll_data{    void *ptr;    int fd;    uint32_t u32;    uint64_t u64; } epoll_data_t;   struct epoll_event {    uint32_t events;  /* Epoll events */    epoll_data_t data;    /* User data variable */ }
复制代码

struct epoll_event的成员events是个bit set,有几种类型:

EPOLLIN:关联的文件是用来读的

EPOLLOUT:关联的文件是用来写的

EPOLLET:Edge Trigger,与之对应的是Level Trigger,下面会详细介绍它们的区别。需要注意的是Level Trigger是默认模式,在我这边(linux-2.6.32)头文件sys/epoll.h中已经没有EPOLLLT的定义了,所以在代码中不要再显式地写EPOLLLT了,反正默认情况用的就是它。select和poll都相当于epoll中的Level Trigger模式。

定义两个变量,后面会用。

structepoll_event  event, events[20];

epoll系列有3组函数:

  1. int  epfd=epoll_create(int size);    //创建一个epoll实例。size表示建议内核开辟的空间。
  2. int nfds=epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);    //准备好读/写的事件存放在参数events中,maxevents是同时监听的最大事件数,timeout是超时返回。
  3. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);      //op的取值有:EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL,表示你要从监听集中添加、去除或修改某个文件描述符。

看个例子就知道该怎么用了:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
 
#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000
 
int main(){
    inti,maxi,listenfd,connfd,sockfd,epfd,nfds;
    intn;
    intyes=1;
    charline[MAXLINE+1];
    socklen_t clilen;
    //声明epoll_event结构体变量,ev用于注册事件,数组用于回传要处理的事件
    structepoll_event ev,events[20];
    //生成用于处理accept的epoll专用文件描述符
    epfd=epoll_create(256);
    structsockaddr_in serveraddr;
    structsockaddr_in clientaddr;
    listenfd=socket(PF_INET,SOCK_STREAM,0);
    //设置套接口选项
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));
    //设置与要处理的事件相关的文件描述符
    ev.data.fd=listenfd;
    //设置要处理的事件类型
    ev.events=EPOLLET|EPOLLIN;
    //注册epoll事件
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
 
    bzero(&serveraddr,sizeof(serveraddr));
    serveraddr.sin_family=AF_INET;
    char*local_addr="127.0.0.1";
    inet_pton(AF_INET,local_addr,&(serveraddr.sin_addr));
    serveraddr.sin_port=htons(SERV_PORT);
    bind(listenfd,(structsockaddr*)&serveraddr,sizeof(serveraddr));
    listen(listenfd,LISTENQ);
    maxi=0;
    while(1){
        //等待epoll事件的发生
        nfds=epoll_wait(epfd,events,20,500);
        //处理所发生的事件
        for(i=0;i<nfds;++i){
            if(events[i].data.fd==listenfd){
                clilen=sizeof(clientaddr);
                connfd=accept(listenfd,(structsockaddr*)&clientaddr,&clilen);
                if(connfd<0){
                    perror("connfd<0");
                    exit(1);
                }
                char*str=inet_ntoa(clientaddr.sin_addr);
                printf("accept a connection from %s\n",str);
                //设置用于读操作的文件描述符
                ev.data.fd=connfd;
                //设置用于注册的读操作事件
                ev.events=EPOLLET|EPOLLIN;
                //注册ev
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            elseif(events[i].events & EPOLLIN){
                sockfd = events[i].data.fd;
                printf("read:");
                if((n=read(sockfd,line,MAXLINE))<0){
                    printf("read error,close connection\n");
                    epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,&ev);
                    close(sockfd);         
                }
                line[n]='\0';
                printf("|%s|\n",line);
            }
        }
    }
    return0;
}

上述代码中,服务端建好套接字listenfd后开始监听它,并把它放到epoll中(第38行)。当有新的连接到来时,第53行的if语句成立,服务端accept该连接,并把该连接的描述符connfd放入epoll中(第67行)。如果TCP连接上有可读事件发生,则第69行的if语句成立,服务端从连接上读取数据后打印在标准输出上,如果读取时发生错误则关闭该连接,同时把相应的connfd从epoll中移除。

下面给一个客户端代码负责写服务端写入数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/perl
 
use IO::Socket;
 
my $host="127.0.0.1";
my $port=5000;
 
my $socket=IO::Socket::INET->new("$host:$port") ordie "create socket error $@";
my $msg_out="1234567890";
print $socket $msg_out;
print"now send over,go to sleep...\n";
 
while(1){
    sleep(1);
}

客户端向服务端写入“1234567890”后并没有关闭连接,而是进入了永久的休眠。

运行程序服务端输出:

accept a connection from 127.0.0.1
read:|12345|

为什么只输出了前5个字节?首先要清楚,上层应用在调用send、recv在TCP连接上收发数据时,send并没有真正地向网络对端发送数据,发送数据的工作中由TCP协议完成的。send仅是检查套接口的发送缓存是否有足够的空间,如果有则send直接将要发送的数据放入缓存区,sned返回成功;如果缓存空间不足,则send阻塞(如果没有设置O_NONBLOCK的话),直到TCP协议发送完缓存区中原有的数据。recv也同样。

Edge Trigger仅当有读/写事件发生时它才触发,上例中client仅发送了一次数据,所以在server端只读取一次,只从缓存区中读取了前MAXLINE(为5)个字节。

Level Trigger只要缓存区中还有数据可读就还会触发。下面的代码采用Level Trigger。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
 
#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000
 
int main(){
    inti,maxi,listenfd,connfd,sockfd,epfd,nfds;
    intn;
    intyes=1;
    charline[MAXLINE+1];
    socklen_t clilen;
    structepoll_event ev,events[20];
    epfd=epoll_create(256);
    structsockaddr_in serveraddr;
    structsockaddr_in clientaddr;
    listenfd=socket(PF_INET,SOCK_STREAM,0);
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));
    ev.data.fd=listenfd;
    ev.events=EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
 
    bzero(&serveraddr,sizeof(serveraddr));
    serveraddr.sin_family=AF_INET;
    char*local_addr="127.0.0.1";
    inet_pton(AF_INET,local_addr,&(serveraddr.sin_addr));
    serveraddr.sin_port=htons(SERV_PORT);
    bind(listenfd,(structsockaddr*)&serveraddr,sizeof(serveraddr));
    listen(listenfd,LISTENQ);
    maxi=0;
    while(1){
        nfds=epoll_wait(epfd,events,20,500);
        for(i=0;i<nfds;++i){
            if(events[i].data.fd==listenfd){
                clilen=sizeof(clientaddr);
                connfd=accept(listenfd,(structsockaddr*)&clientaddr,&clilen);
                if(connfd<0){
                    perror("connfd<0");
                    exit(1);
                }
                char*str=inet_ntoa(clientaddr.sin_addr);
                printf("accept a connection from %s\n",str);
                ev.data.fd=connfd;
                ev.events=EPOLLIN;
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            elseif(events[i].events & EPOLLIN){
                sockfd = events[i].data.fd;
                printf("read:");
                if((n=read(sockfd,line,MAXLINE))<0){
                    printf("read error,close connection\n");
                    epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,&ev);
                    close(sockfd);         
                }
                line[n]='\0';
                printf("|%s|\n",line);
            }
        }
    }
    return0;
}

运行输出:

accept a connection from 127.0.0.1
read:|12345|
read:|67890|

我们要分两种情况来讨论read()系统调用:

ssize_t read(intfd, void *buf, size_t count);

(1)读取文件。出错时返回-1;成功时返回读到的字节数,这个数字大部分情况下等于count,只有当文件中剩余的内容不足count时返回值才小于count,如果已读到文件末尾则返回0。所以要想读取一个文件的全部内容,只需要把read()放到while(true)循环中,当read()的返回值小于count时,就可以肯定文件读完了,可以退出循环了。read()系统调用在用户空间是不设缓存的。

(2)读取套接口。说得更具体些是读取本地socket的接收缓存。这里跟读取文件的不同之处在于:当接收缓存已无数据可读时,read()不会返回0(如果没有设置O_NONBLOCK的话),而是一直阻塞,除非对方断开连接read()才返回0。

要想在Edge Trigger模式下读取缓冲区中的所有数据呢,必须和O_NONBLOCK综合使用,此时当接收缓冲区无数据可读时read()返回一个负值(注意不是0),并且把errno置为EAGAIN或EWOULDBLOCK。这里解释一下read()系统调用在返回失败时的几种可能情况:

返回EINTR:在read之前收到信号被中断。

返回EAGAIN:针对普通的文件描述符(不包括套接口描述符),当设置了O_NONBLOCK,而缓存区中又没有数据可读,则返回该值。

返回EWOULDBLOCK:跟EAGAIN类似,只是EWOULDBLOCK专用于套接口描述符。

在有的系统中EAGAIN也可用于套接口描述符,所以为增强可移植性,代码中应该使用对EAGAIN和EWOULDBLOCK都进行检测。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
 
#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000
 
voidsetnonblocking(intsock){
    intopts;
    opts=fcntl(sock,F_GETFL);
    if(opts<0){
        perror("fcntl_get");
        exit(1);
    }
    opts=opts|O_NONBLOCK;
    if(fcntl(sock,F_SETFL,opts)<0){
        perror("fcntl_set");
        exit(1);
    }
}
 
int main(){
    inti,maxi,listenfd,connfd,sockfd,epfd,nfds;
    intn;
    intyes=1;
    charline[MAXLINE+1];
    socklen_t clilen;
    //声明epoll_event结构体变量,ev用于注册事件,数组用于回传要处理的事件
    structepoll_event ev,events[20];
    //生成用于处理accept的epoll专用文件描述符
    epfd=epoll_create(256);
    structsockaddr_in serveraddr;
    structsockaddr_in clientaddr;
    listenfd=socket(PF_INET,SOCK_STREAM,0);
    //设置套接口选项
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));
    //把socket设为非阻塞
    setnonblocking(listenfd);
    //设置与要处理的事件相关的文件描述符
    ev.data.fd=listenfd;
    //设置要处理的事件类型
    ev.events=EPOLLET|EPOLLIN;
    //注册epoll事件
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
 
    bzero(&serveraddr,sizeof(serveraddr));
    serveraddr.sin_family=AF_INET;
    char*local_addr="127.0.0.1";
    inet_pton(AF_INET,local_addr,&(serveraddr.sin_addr));
    serveraddr.sin_port=htons(SERV_PORT);
    bind(listenfd,(structsockaddr*)&serveraddr,sizeof(serveraddr));
    listen(listenfd,LISTENQ);
    maxi=0;
    while(1){
        //等待epoll事件的发生
        nfds=epoll_wait(epfd,events,20,500);
        //处理所发生的事件
        for(i=0;i<nfds;++i){
            if(events[i].data.fd==listenfd){
                clilen=sizeof(clientaddr);
                connfd=accept(listenfd,(structsockaddr*)&clientaddr,&clilen);
                if(connfd<0){
                    perror("connfd<0");
                    exit(1);
                }
                setnonblocking(connfd);
                char*str=inet_ntoa(clientaddr.sin_addr);
                printf("accept a connection from %s\n",str);
                //设置用于读操作的文件描述符
                ev.data.fd=connfd;
                //设置用于注册的读操作事件
                ev.events=EPOLLET|EPOLLIN;
                //注册ev
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            elseif(events[i].events & EPOLLIN){
                sockfd = events[i].data.fd;
                printf("read:");
                while(1){
                    n=read(sockfd,line,MAXLINE);
                    if(n<0){
                        if(errno==EAGAIN ||errno==EWOULDBLOCK){
                            break;
                        }
                    }
                    elseif(n==0){
                        break;
                    }
                    else{
                        line[n]='\0';
                        printf("|%s|",line);
                    }
                }
                printf("\n");      //printf是行缓冲的,如果一直不输出换行符,第88行和101的内容就不会打印出来的
            }
        }
    }
    return0;
}

运行输出:

accept a connection from 127.0.0.1
read:|12345||67890|

边缘触发是一种高速工作模式,编程上稍微复杂一些。但是在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

最后把上面服务端的程序再扩充一下,server端收到client发来的数据后还要回应一段message,重要的是这个写事件也要放到epoll中。

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
38
39
40
41
42
43
44
45
    nfds=epoll_wait(epfd,events,20,500);
    for(i=0;i<nfds;++i){
        if(events[i].data.fd==listenfd){
            clilen=sizeof(clientaddr);
            connfd=accept(listenfd,(structsockaddr*)&clientaddr,&clilen);
            if(connfd<0){
                perror("connfd<0");
                exit(1);
            }
            char*str=inet_ntoa(clientaddr.sin_addr);
            printf("accept a connection from %s\n",str);
            ev.data.fd=connfd;
            ev.events=EPOLLIN;
            epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
        }
        elseif(events[i].events & EPOLLIN){
            sockfd = events[i].data.fd;
            printf("read:");
            if((n=read(sockfd,line,MAXLINE))<0){
                printf("read error,close connection\n");
                epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,&ev);
                close(sockfd);         
            }
            line[n]='\0';
            printf("|%s|\n",line);
             
            ev.data.fd=sockfd;
            //设置用于注册的写操作事件
            ev.events=EPOLLOUT;
            //修改sockfd上要处理的事件为EPOLLOUT
            epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
        }
        elseif(events[i].events & EPOLLOUT){
            printf("ready to write\n");
            sockfd=events[i].data.fd;
            write(sockfd,line,n);
            //设置用于读操作的文件描述符
            ev.data.fd=sockfd;
            //设置用于注册的读操作事件
            ev.events=EPOLLIN;
            //修改sockfd上要处理的事件为EPOLLIN
            epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
        }
    }
}

运行输出:

accept a connection from 127.0.0.1
read:|12345|
ready to write
read:|67890|
ready to write

select

select允许程序挂起,并等待从不止一个文件描述符的输入,即程序挂起直到有任何一个文件描述符的数据到达。select设置一个变量中的若干位,用来通知哪一个文件描述符已经有数据到达。

#include<sys/types.h>

#include<sys/time.h>

#include<unistd.h>

int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exeptfds,struct timeval*timeout)

numfds是要检查的所有文件描述符中号码最大的加1

readfds读文件描述符集合

writefds写文件描述符集合

exeptfds异常处理文件描述符集合

timeout具体值:

NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止

struct timeval 类型的指针,若等待为 timeout 时间还没有文件描符准备好,就立即返回

0:从不等待,测试所有指定的描述符并立即返回

返回值:readfds、writefds和exeptfds中准备好的fd数目,当触发time expire时会返回0,发生错误时返回-1。

下面的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);用来清除描述词组set的全部位

参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:

structtimeval
{
    time_ttv_sec;//second
    time_ttv_usec;//minisecond 
};

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边PC上sizeof(fd_set)=128,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是128*8=1024。

下面的代码监听标准输入上是否有输入数据,如果有就把它输出到标准输出上。

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
#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
  
int main(){
    fd_set rset;
    FD_ZERO(&rset);
    FD_SET(0,&rset);
  
    structtimeval t;
    t.tv_sec=4;
    t.tv_usec=500000;
  
    intret=select(1,&rset,NULL,NULL,&t);
    charbuf[10]="";
    if(ret>0){
    if(FD_ISSET(0,&rset)){
        read(0,buf,sizeof(buf));
        write(1,buf,strlen(buf));
    }
    }
    return0;
}

上面只是个简单示例,来个复杂一点的,select用于socket编程,实现跟epoll相同的功能。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
 
#define MYPORT 5000
#define BACKLOG 2       //TCP层接收链接池的缓冲队列大小
#define BUF_SIZE 200    //用于读写网络数据的内存缓冲大小
 
int fd_A[BACKLOG];      //存放处于连接中的socket描述符
int conn_amount;        //目前的TCP连接数量
 
//显示目前有几个工作的TCP连接,以及相应的socket描述符
voidshowclient(){
    inti;
    printf("client amount:%d\nready file descriptor:",conn_amount);
    for(i=0;i<conn_amount;++i)
        printf("%d ",fd_A[i]);
    printf("\n");
}
 
int main(){
    intsock_fd,new_fd;
    structsockaddr_in server_addr;
    structsockaddr_in client_addr;
    socklen_t sin_size;
    intyes=1;
    charbuf[BUF_SIZE];
    intret;
    inti;
 
    if((sock_fd=socket(PF_INET,SOCK_STREAM,0))==-1){
        perror("socket");
        exit(1);
    }
 
    if(setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int))==-1){
        perror("socket");
        exit(1);
    }
 
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(MYPORT);
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    memset(server_addr.sin_zero,'\0',sizeof(server_addr.sin_zero));
 
    if(bind(sock_fd,(structsockaddr*)&server_addr,sizeof(server_addr))==-1){
        perror("bind");
        exit(1);
    }
 
    if(listen(sock_fd,BACKLOG)==-1){
        perror("listen");
        exit(1);
    }
 
    printf("listen on port %d\n",MYPORT);
 
    fd_set fdsr;
    intmaxsock;        //存放在监视的最大文件描述符
    structtimeval tv;
 
    conn_amount=0;     //初始连接数量为0
    sin_size=sizeof(client_addr);
    maxsock=sock_fd;
    while(1){
        FD_ZERO(&fdsr);    //清空fdsr
        FD_SET(sock_fd,&fdsr);
        for(i=0;i<BACKLOG;++i){
            if(fd_A[i]!=0){
                FD_SET(fd_A[i],&fdsr);         //把准备就绪的连接全部放入fdsr中
            }
        }
         
        tv.tv_sec=30;
        tv.tv_usec=0;
 
        //监听集合fdsr
        ret=select(maxsock+1,&fdsr,NULL,NULL,&tv);
        if(ret<0){
            perror("select");
            break;
        }elseif(ret==0){
            printf("time out\n");
            continue;
        }
         
        //逐一遍历每个连接,看其是否就绪。若是则读取其上的数据,并返回一串消息
        for(i =0;i<conn_amount;i++){
            if(FD_ISSET(fd_A[i],&fdsr)){
                ret=recv(fd_A[i],buf,sizeof(buf),0);
                charstr[]="Good,very nice!\n";
                send(fd_A[i],str,sizeof(str)+1,0);
                if(ret<=0){
                    printf("client [%d] close\n",i);
                    close(fd_A[i]);
                    FD_CLR(fd_A[i],&fdsr);
                    fd_A[i]=0;
                }else{
                    if(ret<BUF_SIZE)    //若数据量超过了BUF_SIZE,则截断之
                        memset(&buf[ret],'\0',1);
                    printf("client [%d] send:%s\n",i,buf);
                }
            }
        }
 
        if(FD_ISSET(sock_fd,&fdsr)){       //有新的连接请求
            new_fd=accept(sock_fd,(structsockaddr*)&client_addr,&sin_size);
            if(new_fd<=0){
                perror("accept");
                continue;
            }
 
            if(conn_amount<BACKLOG){    
                fd_A[conn_amount++]=new_fd;        //把新的连接socket描述符放到fd_A数组中
                printf("accept connecton from %s\n",inet_ntoa(client_addr.sin_addr));
                if(new_fd>maxsock)          //更新maxsock
                    maxsock=new_fd;
            }
            else{          //如果连接数达到了BACKLOG,则将最后到来的连接关闭掉,不处理它
                printf("max connection arrive,close the last connection\n");
                send(new_fd,"bye",4,0);
                close(new_fd);
            }
        }
        showclient();
    }
 
    for(i=0;i<BACKLOG;i++){
        if(fd_A[i]!=0)
            close(fd_A[i]);
    }
    exit(0);
}

在许多测试中我们会看到如果没有大量的idle-connection或者dead-connection,epoll的效率并不会比 select/poll高很多,但是当我们遇到大量的idle-connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率 大大高于select/poll。

原文来自:博客园(华夏35度)http://www.cnblogs.com/zhangchaoyang作者:Orisun

0 0
原创粉丝点击