1. 程式人生 > >apache worker模式下keepalive及內核keepalive

apache worker模式下keepalive及內核keepalive

一輪 buck syn 而在 truct mod mman 課程設計 要求

一、apache的worker模式下保活選項
由於http的keepalive是控制apache行為的一個重要的特征,所以大致看了一下這個機制的實現及意義。通常來說,如果進行定性的分析,大家都可以隨便信口開河,甚至望文生義也可以胡謅半天,但是這些結論對於真正的應用來說意義極小。就好像你說,我這個結論有90%的正確性,或者說這個程序能夠處理90%的情況。那麽這個結論的意義在不同的情況下就有不同的價值,例如程序在一個課程設計中,99%的正確率是可以完成要求的,對於銀行系統來說,這個肯定是不達標的,對於航天系統中,這個概率更加不可靠。在工程中同樣也是如此,即使很小的概率,那麽在大量訪問面前,它同樣會導致該問題出現,並且從數量上看並不少。

apache對於保活的選項在httpd-2.4.2\modules\http\http_core.c中實現,這個名字起得相當霸氣外露,但是該文件中並沒有太多真正的策略性內容,文件包括註釋308行。而在這個簡短的文件中,就包含了三個對於保活的處理選項,但是這些處理選項只是對於配置文件中配置項的讀取,而真正使用這些配置的代碼在這個文件中體現並不是很明顯。這三個命令的配置為
static const command_rec http_cmds[] = {
AP_INIT_TAKE1("KeepAliveTimeout", set_keep_alive_timeout, NULL, RSRC_CONF,
"Keep-Alive timeout duration (sec)"),
AP_INIT_TAKE1("MaxKeepAliveRequests", set_keep_alive_max, NULL, RSRC_CONF,
"Maximum number of Keep-Alive requests per connection, "
"or 0 for infinite"),
AP_INIT_FLAG("KeepAlive", set_keep_alive, NULL, RSRC_CONF,
"Whether persistent connections should be On or Off"),
{ NULL }
};
這三個函數的實現中合理就不在摘錄了,只是把核心的代碼拷貝一下
static const char *set_keep_alive_timeout(cmd_parms *cmd, void *dummy,
const char *arg)
{
cmd->server->keep_alive_timeout = timeout;
}
static const char *set_keep_alive(cmd_parms *cmd, void *dummy,
int arg)
{
……

cmd->server->keep_alive = arg;
return NULL;
}
二、這些值在什麽時候使用
1、超時參數
worker_thread--->>>process_socket--->>>ap_process_connection--->>ap_run_process_connection--->>>ap_process_http_connection--->>ap_process_http_sync_connection
{
while ((r = ap_read_request(c)) != NULL) {
……
if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted) 對於設置了保活選項的socket,這裏的兩個條件都是不滿足的,所以此時會繼續進行循環,在ap_read_request函數中阻塞
break;
……
apr_socket_opt_set(csd, APR_INCOMPLETE_READ, 1);
apr_socket_timeout_set(csd, c->base_server->keep_alive_timeout);這裏的時間就是在配置文件中配置的超時時間
}
其中阻塞等待的系統調用為
read_request_line--->>>ap_rgetline--->>>ap_rgetline_core--->>>apr_bucket_read-->>socket_bucket_read-->>>apr_socket_recv
while ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK)
&& (sock->timeout > 0)) {
do_select:
arv = apr_wait_for_io_or_timeout(NULL, sock, 1);
這裏就使用了保活的時間設置。從循環的主題來看,如果設置了保活選項,那麽當socket處理完一個socket請求之後,會在套接口的read系統調用中嘗試最多阻塞timeout時間,如果這段時間內沒有新的數據到來,那麽讀操作超時返回,否則開始下一輪讀取和處理。
2、max及使能的使用
max的使用
AP_DECLARE(int) ap_set_keepalive(request_rec *r)
int left = r->server->keep_alive_max - r->connection->keepalives;
……
if(……
&& r->server->keep_alive
&& (r->server->keep_alive_timeout > 0)
&& ((r->server->keep_alive_max == 0)
|| (left > 0))
……

{
……
{

r->connection->keepalive = AP_CONN_KEEPALIVE;
r->connection->keepalives++;
……
}
源代碼中對於這兩項的註釋說明為
/** Are we going to keep the connection alive for another request?
* @see ap_conn_keepalive_e */
ap_conn_keepalive_e keepalive;

/** How many times have we used it? */
int keepalives;
這裏想說明的問題是:如果你設置了keepalive選項,那麽在這個時間段內,即使客戶端沒有發送任何數據過來,這個worker線程始終要嘗試進行這麽長時間的等待,這是一個機械的假設,所以需要根據不同的場合慎重使用。
3、如果沒有設置該選項
worker_thread-->>process_socket
if (current_conn) {
current_conn->current_thread = thd;
ap_process_connection(current_conn, sock);
ap_lingering_close(current_conn);
}
可以看到的是,如果沒有設置保活選項,此時在ap_process_connection之後客戶端的套接口被直接關閉,所以說是一個短連接。
三、內核的keepalive時間
1、內核保活定時器動作
內核同樣有一個保活選項,名字和這個也類似,但是它的意義和apache的意義完全不同,而apache的該功能也不依賴內核實現。內核的保活使用定時器時間,它是一個在雙方長時間沒有交互的情況下主動向對方發送一個探測性報文,以確保對方還存在,當然這個功能在應用層做也是可以的。這裏大家可以先想一下這個探測性報文是怎麽樣的,也就是說這個報文怎麽能夠做到探測並且僅僅是一個探測報文,而不會對正常的報文傳遞造成影響。其定時器超時函數處理為linux-2.6.21\net\ipv4\tcp_timer.c
static void tcp_keepalive_timer (unsigned long data)----->>>tcp_write_wakeup--->>>tcp_xmit_probe_skb(sk, 0)
/* This routine sends a packet with an out of date sequence
* number. It assumes the other end will try to ack it.
*
* Question: what should we make while urgent mode?
* 4.4BSD forces sending single byte of data. We cannot send
* out of window data, because we have SND.NXT==SND.MAX...
*
* Current solution: to send TWO zero-length segments in urgent mode:
* one is with SEG.SEQ=SND.UNA to deliver urgent pointer, another is
* out-of-date with SND.UNA-1 to probe window.
*/
static int tcp_xmit_probe_skb(struct sock *sk, int urgent)
^
TCP_SKB_CB(skb)->flags = TCPCB_FLAG_ACK;
TCP_SKB_CB(skb)->sacked = urgent;
^
/* Use a previous sequence. This should cause the other
* end to send an ack. Don‘t queue or clone SKB, just
* send it.

*/
TCP_SKB_CB(skb)->seq = urgent ? tp->snd_una : tp->snd_una - 1;
TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(skb)->seq;
TCP_SKB_CB(skb)->when = tcp_time_stamp;
return tcp_transmit_skb(sk, skb, 0, GFP_ATOMIC);
這裏探測的方法就是向對方發送一過時的報文,例如對方已經確認了第3個報文被收到,此時我們發送一個過時的報文2,此時對方如果收到這個報文,它會檢測出這是個過時的報文,所以會對這個報文進行回應,並且在回應中強調自己希望收到的是第4個報文,此時保活定時器就可以有機會清掉保活定時器。
2、對於窗口外報文的處理
linux-2.6.21\net\ipv4\tcp_input.c
int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
……
if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {不在滑動窗口可接收範圍之間
/* RFC793, page 37: "In all states except SYN-SENT, all reset
* (RST) segments are validated by checking their SEQ-fields."
* And page 69: "If an incoming segment is not acceptable,
* an acknowledgment should be sent in reply (unless the RST bit
* is set, if so drop the segment and return)".
*/
if (!th->rst)
tcp_send_dupack(sk, skb);回傳一個重復的確認報文
goto discard;
}

apache worker模式下keepalive及內核keepalive