文章目錄
Linux定時方法
Linux中為我們提供了三種定時方法,分別是Socket超時選項,SIGALRM信號,I/O復用超時參數。下面一一對其進行介紹。
Socket超時選項 socket中的SO_RCVTIMEO 和SO_SNDTIMEO 選項分別用來設置接收數據超時時間 和發送數據超時時間 。所以這兩個選項僅僅適用于那些用來收發數據的socket系統調用,如send,recv,recvmsg,accept,connect。
下面是這兩個選項對這些系統調用的影響
I/O復用超時參數 在Linux下的三組I/O復用系統調用都帶有超時參數,所以他們不僅可以統一處理信號和I/O時間,也能統一處理定時事件。 但是I/O復用系統調用可能會在超時時間到期之前提前返回(有I/O事件就緒),所以如果要使用該參數進行定時,就需要不斷更新定時參數來反應剩余的時間。
SIGALRM信號 Linux下的alarm函數和setitimer函數 也可以用于設定鬧鐘,一旦鬧鐘到時,就會觸發SIGALRM信號 ,所以我們可以利用該信號的處理函數來處理定時任務。
# include <unistd.h>
unsigned int alarm ( unsigned int seconds
) ; # include <sys/time.h>
int setitimer ( int which
, const struct itimerval * new_value
, struct itimerval * old_value
) ; struct itimerval { struct timeval it_interval
; struct timeval it_value
;
} ; struct timeval { time_t tv_sec
; suseconds_t tv_usec
;
} ;
定時器鏈表
由于服務器程序通常管理著眾多定時事件,因此有效地組織這些定時事件,使他們能夠在預期的時間點被觸發并且不影響服務器的主要邏輯,對于服務器的性能有著至關重要的影響。因此我們通常會將每個定時事件封裝成定時器,并利用某種容器類數據結構對定時事件進行統一管理。
下面就使用一個以到期時間進行升序排序的雙向帶頭尾節點的鏈表 來作為容器,實現定時器鏈表。 具體的細節以及實現思路都寫在了注釋里。
# ifndef __TIMER_LIST_H__
# define __TIMER_LIST_H__ # include <time.h>
# include <stdio.h>
# include <sys/socket.h>
# include <sys/types.h>
# include <netinet/in.h> const int MAX_BUFFER_SIZE
= 1024 ; class util_timer ;
struct client_data
{ sockaddr_in addr
; int sock_fd
; char buff
[ MAX_BUFFER_SIZE
] ; util_timer
* timer
;
} ;
struct util_timer
{ public : util_timer ( ) : _next ( nullptr ) , _prev ( nullptr ) { } time_t _expire
; void ( * fun
) ( client_data
* ) ; client_data
* _user_data
; util_timer
* _next
; util_timer
* _prev
;
} ;
class timer_list
{ typedef util_timer node
; public : timer_list ( ) : _head ( nullptr ) , _tail ( nullptr ) { } ~ timer_list ( ) { node
* cur
= _head
; while ( cur
) { node
* next
= cur
-> _next
; delete cur
; cur
= next
; } } void push ( node
* timer
) { if ( timer
== nullptr ) { return ; } if ( _head
== nullptr ) { _head
= _tail
= timer
; return ; } if ( timer
-> _expire
< _head
-> _expire
) { timer
-> _next
= _head
; _head
-> _prev
= timer
; _head
= timer
; return ; } node
* prev
= _head
; node
* cur
= _head
-> _next
; while ( cur
) { if ( timer
-> _expire
< cur
-> _expire
) { timer
-> _next
= cur
; cur
-> _prev
= timer
; prev
-> _next
= timer
; timer
-> _prev
= prev
; return ; } prev
= cur
; cur
= cur
-> _next
; } if ( cur
== nullptr ) { prev
-> _next
= timer
; timer
-> _prev
= prev
; timer
-> _next
= nullptr ; _tail
= timer
; } } void adjust_node ( node
* timer
) { if ( timer
== nullptr ) { return ; } if ( timer
== _head
&& timer
== _tail
) { _head
= _tail
= nullptr ; } if ( timer
== _head
) { _head
= timer
-> _next
; if ( _head
) { _head
-> _prev
= nullptr ; } } if ( timer
== _tail
) { _tail
= _tail
-> _prev
; if ( _tail
) { _tail
-> _next
= nullptr ; } } else { timer
-> _prev
-> _next
= timer
-> _next
; timer
-> _next
-> _prev
= timer
-> _prev
; } push ( timer
) ; } void pop ( node
* timer
) { if ( timer
== nullptr ) { return ; } if ( timer
== _head
&& timer
== _tail
) { delete timer
; _head
= _tail
= nullptr ; } else if ( timer
== _head
) { _head
= _head
-> _next
; _head
-> _prev
= nullptr ; delete timer
; timer
= nullptr ; } else if ( timer
== _tail
) { _tail
= _tail
-> _prev
; _tail
-> _next
= nullptr ; delete timer
; timer
= nullptr ; } else { timer
-> _prev
-> _next
= timer
-> _next
; timer
-> _next
-> _prev
= timer
-> _prev
; delete timer
; timer
= nullptr ; } } void tick ( ) { if ( _head
== nullptr ) { return ; } printf ( "time tick\n" ) ; time_t cur_time
= time ( nullptr ) ; node
* cur
= _head
; while ( cur
) { if ( cur
-> _expire
> cur_time
) { break ; } cur
-> fun ( cur
-> _user_data
) ; node
* next
= cur
-> _next
; if ( next
!= nullptr ) { next
-> _prev
= nullptr ; } delete cur
; cur
= next
; } } private : node
* _head
; node
* _tail
;
} ;
# endif
時間復雜度 添加節點:O(n) 刪除節點:O(1) 執行定時任務:O(1)
空閑斷開
對于服務器來說,定期處理非活動連接 是保證其高可用 的一項不必可少的功能,下面就以上一篇博客中實現的統一事件源服務器 舉例,演示一下如何使用定時器鏈表 來實現空閑斷開 的功能。 Linux網絡編程 | 信號 :信號函數、信號集、統一事件源 、網絡編程相關信號
實現的思路很簡單,我們為每一個連接設定一個定時器,將定時器放入定時器鏈表中,并通過alarm函數來周期性觸發SIGALRM信號。 如果某一個連接當前有新的活動,則說明該連接為活躍連接,重置其定時器并且調整定時器在鏈表中的位置。 我們設定一個監控周期,每當周期到則會觸發SIGALRM信號,信號處理函數則利用管道將信號發送給主循環。如果主循環監控的管道讀端有數據,并且待處理信號為SIGALRM,則說明此時需要執行定時器鏈表上的定時任務(關閉當前不活躍的連接)。
# include <fcntl.h>
# include <signal.h>
# include <stdlib.h>
# include <sys/socket.h>
# include <sys/epoll.h>
# include <sys/types.h>
# include <arpa/inet.h>
# include <stdio.h>
# include <errno.h>
# include <netinet/in.h>
# include <unistd.h> # include "timer_list.h"
const int MAX_LISTEN
= 5 ;
const int MAX_EVENT
= 1024 ;
const int MAX_BUFFER
= 1024 ;
const int TIMESLOT
= 5 ;
const int FD_LIMIT
= 65535 ; static int pipefd
[ 2 ] ;
static int epoll_fd
= 0 ;
static timer_list timer_lst
;
int setnonblocking ( int fd
)
{ int flag
= fcntl ( fd
, F_GETFL
, 0 ) ; fcntl ( fd
, F_SETFL
, flag
|= O_NONBLOCK
) ; return flag
;
}
void epoll_add_fd ( int epoll_fd
, int fd
)
{ struct epoll_event event
; event
. data
. fd
= fd
; event
. events
= EPOLLIN
| EPOLLET
; epoll_ctl ( epoll_fd
, EPOLL_CTL_ADD
, fd
, & event
) ;
}
void sig_handler ( int sig
)
{ int save_errno
= errno
; send ( pipefd
[ 1 ] , ( char * ) & sig
, 1 , 0 ) ; errno
= save_errno
;
}
void set_sig_handler ( int sig
)
{ struct sigaction sa
; sa
. sa_handler
= sig_handler
; sa
. sa_flags
|= SA_RESTART
; sigfillset ( & sa
. sa_mask
) ; if ( sigaction ( sig
, & sa
, NULL ) < 0 ) { exit ( EXIT_FAILURE
) ; }
}
void timer_handler ( )
{ timer_lst
. tick ( ) ; alarm ( TIMESLOT
) ;
}
void handler ( client_data
* user_data
)
{ if ( user_data
== nullptr ) { return ; } epoll_ctl ( epoll_fd
, EPOLL_CTL_DEL
, user_data
-> sock_fd
, NULL ) ; close ( user_data
-> sock_fd
) ; printf ( "close fd : %d\n" , user_data
-> sock_fd
) ;
} int main ( int argc
, char * argv
[ ] )
{ if ( argc
<= 2 ) { printf ( "輸入參數:IP地址 端口號\n" ) ; return 1 ; } const char * ip
= argv
[ 1 ] ; int port
= atoi ( argv
[ 2 ] ) ; int listen_fd
= socket ( PF_INET
, SOCK_STREAM
, 0 ) ; if ( listen_fd
== - 1 ) { printf ( "listen_fd socket.\n" ) ; return - 1 ; } struct sockaddr_in addr
; addr
. sin_family
= AF_INET
; addr
. sin_port
= htons ( port
) ; addr
. sin_addr
. s_addr
= inet_addr ( ip
) ; if ( bind ( listen_fd
, ( struct sockaddr * ) & addr
, sizeof ( addr
) ) < 0 ) { printf ( "listen_fd bind.\n" ) ; return - 1 ; } if ( listen ( listen_fd
, MAX_LISTEN
) < 0 ) { printf ( "listen_fd listen.\n" ) ; return - 1 ; } int epoll_fd
= epoll_create ( MAX_LISTEN
) ; if ( epoll_fd
== - 1 ) { printf ( "epoll create.\n" ) ; return - 1 ; } epoll_add_fd ( epoll_fd
, listen_fd
) ; if ( socketpair ( PF_UNIX
, SOCK_STREAM
, 0 , pipefd
) < 0 ) { printf ( "socketpair.\n" ) ; return - 1 ; } setnonblocking ( pipefd
[ 1 ] ) ; epoll_add_fd ( epoll_fd
, pipefd
[ 0 ] ) ; set_sig_handler ( SIGALRM
) ; set_sig_handler ( SIGTERM
) ; struct epoll_event events
[ MAX_LISTEN
] ; client_data
* users
= new client_data
[ FD_LIMIT
] ; bool stop_server
= false ; bool time_out
; alarm ( TIMESLOT
) ; while ( ! stop_server
) { int number
= epoll_wait ( epoll_fd
, events
, MAX_LISTEN
, - 1 ) ; if ( number
< 0 && errno
!= EINTR
) { printf ( "epoll_wait.\n" ) ; break ; } for ( int i
= 0 ; i
< number
; i
++ ) { int sock_fd
= events
[ i
] . data
. fd
; if ( sock_fd
== listen_fd
) { struct sockaddr_in clinet_addr
; socklen_t len
= sizeof ( clinet_addr
) ; int conn_fd
= accept ( listen_fd
, ( struct sockaddr * ) & clinet_addr
, & len
) ; if ( conn_fd
< 0 ) { printf ( "accept.\n" ) ; continue ; } epoll_add_fd ( epoll_fd
, sock_fd
) ; users
[ conn_fd
] . addr
= clinet_addr
; users
[ conn_fd
] . sock_fd
= conn_fd
; util_timer
* timer
= new util_timer
; users
-> timer
= timer
; timer
-> _user_data
= & users
[ conn_fd
] ; timer
-> fun
= handler
; time_t cur_time
= time ( nullptr ) ; timer
-> _expire
= cur_time
+ 3 * TIMESLOT
; timer_lst
. push ( timer
) ; } else if ( sock_fd
== pipefd
[ 0 ] && events
[ i
] . events
& EPOLLIN
) { int sig
; char signals
[ MAX_BUFFER
] ; int ret
= recv ( pipefd
[ 0 ] , signals
, MAX_BUFFER
, 0 ) ; if ( ret
== - 1 ) { continue ; } else if ( ret
== 0 ) { continue ; } else { for ( int j
= 0 ; j
< ret
; j
++ ) { switch ( signals
[ i
] ) { case SIGALRM
: { time_out
= true ; break ; } case SIGINT
: { stop_server
= true ; } } } } } else if ( events
[ i
] . events
& EPOLLIN
) { int ret
= recv ( sock_fd
, users
[ sock_fd
] . buff
, MAX_BUFFER
- 1 , 0 ) ; util_timer
* timer
= users
[ sock_fd
] . timer
; if ( ret
< 0 ) { if ( errno
!= EAGAIN
) { handler ( & users
[ sock_fd
] ) ; if ( timer
) { timer_lst
. pop ( timer
) ; } } } else if ( ret
== 0 ) { handler ( & users
[ sock_fd
] ) ; if ( timer
) { timer_lst
. pop ( timer
) ; } } else { if ( timer
) { time_t cur_time
= time ( nullptr ) ; timer
-> _expire
= cur_time
+ 3 * TIMESLOT
; timer_lst
. adjust_node ( timer
) ; } } } else { } } if ( time_out
== true ) { timer_handler ( ) ; time_out
= false ; } } close ( listen_fd
) ; close ( pipefd
[ 1 ] ) ; close ( pipefd
[ 0 ] ) ; delete [ ] users
; return 0 ;
}
總結
以上是生活随笔 為你收集整理的Linux网络编程 | 定时事件 :Linux常见定时方法、定时器链表、空闲断开 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。