欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。关注公粽号 《机器和智能》 回复关键词 “python项目实战” 即可获取美哆商城视频资源!
博主介绍: CSDN优质创作者,CSDN实力新星,CSDN内容合伙人; 阿里云社区专家博主; 华为云社区云享专家; 51CTO社区入驻博主,掘金社区入驻博主,支付宝社区入驻博主,博客园博主。
epoll详解
事件模型 基于管道epoll ET触发模式 基于网络C/S模型的epoll ET触发模式 基于网络C/S非阻塞模型的epoll ET触发模式 epoll的三种工作模式 推书推荐
专栏:《网络编程》
事件模型
EPOLL事件有两种模型:
Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。 Level Triggered (LT) 水平触发只要有数据都会触发。 思考如下步骤:
1.假定我们已经把一个用来从管道中读取数据的文件描述符(RFD)添加到epoll描述符。 2.管道的另一端写入了2KB的数据。 3.调用epoll_wait,并且它会返回RFD,说明它已经准备好读取操作。 4.读取1KB的数据。 5.调用epoll_wait…… 在这个过程中,有两种工作模式:
ET模式
ET模式即Edge Triggered工作模式。 如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。
基于非阻塞文件句柄 只有当read或者write返回EAGAIN(非阻塞读,暂时无数据)时才需要挂起、等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。 LT模式
LT模式即Level Triggered工作模式。 与ET模式不同的是,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,无论后面的数据是否被使用。 LT(level triggered):LT是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。 ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once).
基于管道epoll ET触发模式
# include
# include
# include
# include
# include
# define MAXLINE 10
int main ( int argc
, char * argv
[ ] )
{
int efd
, i
;
int pfd
[ 2 ] ;
pid_t pid
;
char buf
[ MAXLINE
] , ch
= 'a' ;
pipe ( pfd
) ;
pid
= fork ( ) ;
if ( pid
== 0 ) {
close ( pfd
[ 0 ] ) ;
while ( 1 ) {
for ( i
= 0 ; i
< MAXLINE
/ 2 ; i
++ )
buf
[ i
] = ch
;
buf
[ i
- 1 ] = '\n' ;
ch
++ ;
for ( ; i
< MAXLINE
; i
++ )
buf
[ i
] = ch
;
buf
[ i
- 1 ] = '\n' ;
ch
++ ;
write ( pfd
[ 1 ] , buf
, sizeof ( buf
) ) ;
sleep ( 2 ) ;
}
close ( pfd
[ 1 ] ) ;
} else if ( pid
> 0 ) {
struct epoll_event event
;
struct epoll_event resevent
[ 10 ] ;
int res
, len
;
close ( pfd
[ 1 ] ) ;
efd
= epoll_create ( 10 ) ;
/* event.events = EPOLLIN; */
event
. events
= EPOLLIN
| EPOLLET
; /* ET 边沿触发 ,默认是水平触发 */
event
. data
. fd
= pfd
[ 0 ] ;
epoll_ctl ( efd
, EPOLL_CTL_ADD
, pfd
[ 0 ] , & event
) ;
while ( 1 ) {
res
= epoll_wait ( efd
, resevent
, 10 , - 1 ) ;
printf ( "res %d\n" , res
) ;
if ( resevent
[ 0 ] . data
. fd
== pfd
[ 0 ] ) {
len
= read ( pfd
[ 0 ] , buf
, MAXLINE
/ 2 ) ;
write ( STDOUT_FILENO
, buf
, len
) ;
}
}
close ( pfd
[ 0 ] ) ;
close ( efd
) ;
} else {
perror ( "fork" ) ;
exit ( - 1 ) ;
}
return 0 ;
}
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 基于网络C/S模型的epoll ET触发模式
server
/* server.c */
# include
# include
# include
# include
# include
# include
# include
# include
# include
# define MAXLINE 10
# define SERV_PORT 8080
int main ( void )
{
struct sockaddr_in servaddr
, cliaddr
;
socklen_t cliaddr_len
;
int listenfd
, connfd
;
char buf
[ MAXLINE
] ;
char str
[ INET_ADDRSTRLEN
] ;
int i
, efd
;
listenfd
= socket ( AF_INET
, SOCK_STREAM
, 0 ) ;
bzero ( & servaddr
, sizeof ( servaddr
) ) ;
servaddr
. sin_family
= AF_INET
;
servaddr
. sin_addr
. s_addr
= htonl ( INADDR_ANY
) ;
servaddr
. sin_port
= htons ( SERV_PORT
) ;
bind ( listenfd
, ( struct sockaddr * ) & servaddr
, sizeof ( servaddr
) ) ;
listen ( listenfd
, 20 ) ;
struct epoll_event event
;
struct epoll_event resevent
[ 10 ] ;
int res
, len
;
efd
= epoll_create ( 10 ) ;
event
. events
= EPOLLIN
| EPOLLET
; /* ET 边沿触发 ,默认是水平触发 */
printf ( "Accepting connections ...\n" ) ;
cliaddr_len
= sizeof ( cliaddr
) ;
connfd
= accept ( listenfd
, ( struct sockaddr * ) & cliaddr
, & cliaddr_len
) ;
printf ( "received from %s at PORT %d\n" ,
inet_ntop ( AF_INET
, & cliaddr
. sin_addr
, str
, sizeof ( str
) ) ,
ntohs ( cliaddr
. sin_port
) ) ;
event
. data
. fd
= connfd
;
epoll_ctl ( efd
, EPOLL_CTL_ADD
, connfd
, & event
) ;
while ( 1 ) {
res
= epoll_wait ( efd
, resevent
, 10 , - 1 ) ;
printf ( "res %d\n" , res
) ;
if ( resevent
[ 0 ] . data
. fd
== connfd
) {
len
= read ( connfd
, buf
, MAXLINE
/ 2 ) ;
write ( STDOUT_FILENO
, buf
, len
) ;
}
}
return 0 ;
}
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 client
/* client.c */
# include
# include
# include
# include
# define MAXLINE 10
# define SERV_PORT 8080
int main ( int argc
, char * argv
[ ] )
{
struct sockaddr_in servaddr
;
char buf
[ MAXLINE
] ;
int sockfd
, i
;
char ch
= 'a' ;
sockfd
= socket ( AF_INET
, SOCK_STREAM
, 0 ) ;
bzero ( & servaddr
, sizeof ( servaddr
) ) ;
servaddr
. sin_family
= AF_INET
;
inet_pton ( AF_INET
, "127.0.0.1" , & servaddr
. sin_addr
) ;
servaddr
. sin_port
= htons ( SERV_PORT
) ;
connect ( sockfd
, ( struct sockaddr * ) & servaddr
, sizeof ( servaddr
) ) ;
while ( 1 ) {
for ( i
= 0 ; i
< MAXLINE
/ 2 ; i
++ )
buf
[ i
] = ch
;
buf
[ i
- 1 ] = '\n' ;
ch
++ ;
for ( ; i
< MAXLINE
; i
++ )
buf
[ i
] = ch
;
buf
[ i
- 1 ] = '\n' ;
ch
++ ;
write ( sockfd
, buf
, sizeof ( buf
) ) ;
sleep ( 10 ) ;
}
Close ( sockfd
) ;
return 0 ;
}
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 基于网络C/S非阻塞模型的epoll ET触发模式
server
/* server.c */
# include
# include
# include
# include
# include
# include
# include
# include
# include
# define MAXLINE 10
# define SERV_PORT 8080
int main ( void )
{
struct sockaddr_in servaddr
, cliaddr
;
socklen_t cliaddr_len
;
int listenfd
, connfd
;
char buf
[ MAXLINE
] ;
char str
[ INET_ADDRSTRLEN
] ;
int i
, efd
, flag
;
listenfd
= socket ( AF_INET
, SOCK_STREAM
, 0 ) ;
bzero ( & servaddr
, sizeof ( servaddr
) ) ;
servaddr
. sin_family
= AF_INET
;
servaddr
. sin_addr
. s_addr
= htonl ( INADDR_ANY
) ;
servaddr
. sin_port
= htons ( SERV_PORT
) ;
bind ( listenfd
, ( struct sockaddr * ) & servaddr
, sizeof ( servaddr
) ) ;
listen ( listenfd
, 20 ) ;
struct epoll_event event
;
struct epoll_event resevent
[ 10 ] ;
int res
, len
;
efd
= epoll_create ( 10 ) ;
/* event.events = EPOLLIN; */
event
. events
= EPOLLIN
| EPOLLET
; /* ET 边沿触发 ,默认是水平触发 */
printf ( "Accepting connections ...\n" ) ;
cliaddr_len
= sizeof ( cliaddr
) ;
connfd
= accept ( listenfd
, ( struct sockaddr * ) & cliaddr
, & cliaddr_len
) ;
printf ( "received from %s at PORT %d\n" ,
inet_ntop ( AF_INET
, & cliaddr
. sin_addr
, str
, sizeof ( str
) ) ,
ntohs ( cliaddr
. sin_port
) ) ;
flag
= fcntl ( connfd
, F_GETFL
) ;
flag
|= O_NONBLOCK
;
fcntl ( connfd
, F_SETFL
, flag
) ;
event
. data
. fd
= connfd
;
epoll_ctl ( efd
, EPOLL_CTL_ADD
, connfd
, & event
) ;
while ( 1 ) {
printf ( "epoll_wait begin\n" ) ;
res
= epoll_wait ( efd
, resevent
, 10 , - 1 ) ;
printf ( "epoll_wait end res %d\n" , res
) ;
if ( resevent
[ 0 ] . data
. fd
== connfd
) {
while ( ( len
= read ( connfd
, buf
, MAXLINE
/ 2 ) ) > 0 )
write ( STDOUT_FILENO
, buf
, len
) ;
}
}
return 0 ;
}
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 client
/* client.c */
# include
# include
# include
# include
# define MAXLINE 10
# define SERV_PORT 8080
int main ( int argc
, char * argv
[ ] )
{
struct sockaddr_in servaddr
;
char buf
[ MAXLINE
] ;
int sockfd
, i
;
char ch
= 'a' ;
sockfd
= socket ( AF_INET
, SOCK_STREAM
, 0 ) ;
bzero ( & servaddr
, sizeof ( servaddr
) ) ;
servaddr
. sin_family
= AF_INET
;
inet_pton ( AF_INET
, "127.0.0.1" , & servaddr
. sin_addr
) ;
servaddr
. sin_port
= htons ( SERV_PORT
) ;
connect ( sockfd
, ( struct sockaddr * ) & servaddr
, sizeof ( servaddr
) ) ;
while ( 1 ) {
for ( i
= 0 ; i
< MAXLINE
/ 2 ; i
++ )
buf
[ i
] = ch
;
buf
[ i
- 1 ] = '\n' ;
ch
++ ;
for ( ; i
< MAXLINE
; i
++ )
buf
[ i
] = ch
;
buf
[ i
- 1 ] = '\n' ;
ch
++ ;
write ( sockfd
, buf
, sizeof ( buf
) ) ;
sleep ( 10 ) ;
}
Close ( sockfd
) ;
return 0 ;
}
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 epoll的三种工作模式
水平触发模式 - (根据读来解释)
只要fd对应的缓冲区有数据,epoll_wait就会返回 返回的次数与发送数据的次数没有关系 epoll默认的工作模式 边沿触发模式 - ET
fd - 默认阻塞属性 客户端给server发数据: 发一次数据server 的 epoll_wait就返回一次 不在乎数据是否读完 如果读不完,如何把数据全部读出来? while(recv()); 数据读完之后recv会阻塞 解决阻塞问题 —— 设置非阻塞fd 边沿非阻塞触发
水平触发模式会多次返回,只要server的read缓冲区有数据,epoll_wait就返回,也就会通知server去读数据,那么在循环检测的时候,只要server的read缓冲区有数据,epoll_wait就会多次调用,多次返回,并通知server去读数据;假如说client发送过了100个数据,也就是serve的read缓冲区有100个数据,但是调用recv函数的时候只能读50个数据,而本次循环只调用了一次recv,那么只能下次循环再读剩余的50个数据,所以下次循环检测的时候,epoll_wait还是会返回,因为缓冲区还是剩余数据。这就是水平触发模式。这样的话虽然client只发了1次,但是epoll_wait会通知两次server去读数据。 —— (printf函数是标准C库函数,C库函数都有一个默认缓冲区,printf的大小是8K。printf函数是行缓冲,使用printf函数的时候,如果不加 \n 会默认等到写满的时候才打印内容,加 \n 会强制把缓冲区的内容打印出来。另外 \0 表示结束,不加 \0 就会一直输出直到遇到 \0,用write(STDOUT_FILENO)替代printf函数就可以解决这些问题。) 边沿触发模式,client发一次数据epoll_wait只返回一次,也就只读一次,这样的话server的read缓冲区可能会有很多数据堆积,server读数据的时候可能读到的是上一次剩余的数据,并且只有client发的时候,epoll_wait才会通知server去读数据,边沿触发模式尽可能减少了epoll_wait的调用次数,缺点是数据有可能读不完导致堆积。
推书推荐
书名:《速学Linux:系统应用从入门到精通》 购买链接:点击购买
如果你是刚刚开始学习Linux的小白,那么本书可作为入门宝典,带你快速入门Linux。 如果你希望获得更多超值内容,那么本书为你提供150段教学视频+电子教案+学习资料,更有价值50元的5节精品线上课程。 如果你希望获得更多实战经验,那么本书提供了47个知识拓展和220个动手练习