自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

幾種經(jīng)典的網(wǎng)絡(luò)服務(wù)器架構(gòu)模型的分析與比較

開發(fā) 架構(gòu)
事件驅(qū)動為廣大的程序員所熟悉,其最為人津津樂道的是在圖形化界面編程中的應(yīng)用;事實上,在網(wǎng)絡(luò)編程中事件驅(qū)動也被廣泛使用,并大規(guī)模部署在高連接數(shù)高吞吐量的服務(wù)器程序中,如 http 服務(wù)器程序、ftp 服務(wù)器程序等。相比于傳統(tǒng)的網(wǎng)絡(luò)編程方式,事件驅(qū)動能夠極大的降低資源占用,增大服務(wù)接待能力,并提高網(wǎng)絡(luò)傳輸效率。

前言

事件驅(qū)動為廣大的程序員所熟悉,其最為人津津樂道的是在圖形化界面編程中的應(yīng)用;事實上,在網(wǎng)絡(luò)編程中事件驅(qū)動也被廣泛使用,并大規(guī)模部署在高連接數(shù)高吞吐量的服務(wù)器程序中,如 http 服務(wù)器程序、ftp 服務(wù)器程序等。相比于傳統(tǒng)的網(wǎng)絡(luò)編程方式,事件驅(qū)動能夠極大的降低資源占用,增大服務(wù)接待能力,并提高網(wǎng)絡(luò)傳輸效率。

關(guān)于本文提及的服務(wù)器模型,搜索網(wǎng)絡(luò)可以查閱到很多的實現(xiàn)代碼,所以,本文將不拘泥于源代碼的陳列與分析,而側(cè)重模型的介紹和比較。使用 libev 事件驅(qū)動庫的服務(wù)器模型將給出實現(xiàn)代碼。

本文涉及到線程 / 時間圖例,只為表明線程在各個 IO 上確實存在阻塞時延,但并不保證時延比例的正確性和 IO 執(zhí)行先后的正確性;另外,本文所提及到的接口也只是筆者熟悉的 Unix/Linux 接口,并未推薦 Windows 接口,讀者可以自行查閱對應(yīng)的 Windows 接口。

阻塞型的網(wǎng)絡(luò)編程接口

 

 

幾乎所有的程序員***次接觸到的網(wǎng)絡(luò)編程都是從 listen()、send()、recv()等接口開始的。使用這些接口可以很方便的構(gòu)建服務(wù)器 /客戶機(jī)的模型。

我們假設(shè)希望建立一個簡單的服務(wù)器程序,實現(xiàn)向單個客戶機(jī)提供類似于“一問一答”的內(nèi)容服務(wù)。

圖 1. 簡單的一問一答的服務(wù)器 /客戶機(jī)模型

圖 1. 簡單的一問一答的服務(wù)器 / 客戶機(jī)模型

我們注意到,大部分的 socket接口都是阻塞型的。所謂阻塞型接口是指系統(tǒng)調(diào)用(一般是 IO接口)不返回調(diào)用結(jié)果并讓當(dāng)前線程一直阻塞,只有當(dāng)該系統(tǒng)調(diào)用獲得結(jié)果或者超時出錯時才返回。

實際上,除非特別指定,幾乎所有的 IO接口 (包括 socket 接口 )都是阻塞型的。這給網(wǎng)絡(luò)編程帶來了一個很大的問題,如在調(diào)用 send()的同時,線程將被阻塞,在此期間,線程將無法執(zhí)行任何運(yùn)算或響應(yīng)任何的網(wǎng)絡(luò)請求。這給多客戶機(jī)、多業(yè)務(wù)邏輯的網(wǎng)絡(luò)編程帶來了挑戰(zhàn)。這時,很多程序員可能會選擇多線程的方式來解決這個問題。
 

 

多線程服務(wù)器程序

應(yīng)對多客戶機(jī)的網(wǎng)絡(luò)應(yīng)用,最簡單的解決方式是在服務(wù)器端使用多線程(或多進(jìn)程)。多線程(或多進(jìn)程)的目的是讓每個連接都擁有獨(dú)立的線程(或進(jìn)程),這樣任何一個連接的阻塞都不會影響其他的連接。

具體使用多進(jìn)程還是多線程,并沒有一個特定的模式。傳統(tǒng)意義上,進(jìn)程的開銷要遠(yuǎn)遠(yuǎn)大于線程,所以,如果需要同時為較多的客戶機(jī)提供服務(wù),則不推薦使用多進(jìn)程;如果單個服務(wù)執(zhí)行體需要消耗較多的 CPU 資源,譬如需要進(jìn)行大規(guī)模或長時間的數(shù)據(jù)運(yùn)算或文件訪問,則進(jìn)程較為安全。通常,使用 pthread_create () 創(chuàng)建新線程,fork() 創(chuàng)建新進(jìn)程。

我們假設(shè)對上述的服務(wù)器 / 客戶機(jī)模型,提出更高的要求,即讓服務(wù)器同時為多個客戶機(jī)提供一問一答的服務(wù)。于是有了如下的模型。

圖 2. 多線程服務(wù)器模型

圖 2. 多線程的服務(wù)器模型 

在上述的線程 / 時間圖例中,主線程持續(xù)等待客戶端的連接請求,如果有連接,則創(chuàng)建新線程,并在新線程中提供為前例同樣的問答服務(wù)。

很多初學(xué)者可能不明白為何一個 socket 可以 accept 多次。實際上,socket 的設(shè)計者可能特意為多客戶機(jī)的情況留下了伏筆,讓 accept() 能夠返回一個新的 socket。下面是 accept 接口的原型:

  1. int accept(int s, struct sockaddr *addr, socklen_t *addrlen);  

輸入?yún)?shù) s 是從 socket(),bind() 和 listen() 中沿用下來的 socket 句柄值。執(zhí)行完 bind() 和 listen() 后,操作系統(tǒng)已經(jīng)開始在指定的端口處監(jiān)聽所有的連接請求,如果有請求,則將該連接請求加入請求隊列。調(diào)用 accept() 接口正是從 socket s 的請求隊列抽取***個連接信息,創(chuàng)建一個與 s 同類的新的 socket 返回句柄。新的 socket 句柄即是后續(xù) read() 和 recv() 的輸入?yún)?shù)。如果請求隊列當(dāng)前沒有請求,則 accept() 將進(jìn)入阻塞狀態(tài)直到有請求進(jìn)入隊列。

上述多線程的服務(wù)器模型似乎***的解決了為多個客戶機(jī)提供問答服務(wù)的要求,但其實并不盡然。如果要同時響應(yīng)成百上千路的連接請求,則無論多線程還是多進(jìn)程都會嚴(yán)重占據(jù)系統(tǒng)資源,降低系統(tǒng)對外界響應(yīng)效率,而線程與進(jìn)程本身也更容易進(jìn)入假死狀態(tài)。

很多程序員可能會考慮使用“線程池”或“連接池”。“線程池”旨在減少創(chuàng)建和銷毀線程的頻率,其維持一定合理數(shù)量的線程,并讓空閑的線程重新承擔(dān)新的執(zhí)行任務(wù)。“連接池”維持連接的緩存池,盡量重用已有的連接、減少創(chuàng)建和關(guān)閉連接的頻率。這兩種技術(shù)都可以很好的降低系統(tǒng)開銷,都被廣泛應(yīng)用很多大型系統(tǒng),如 websphere、tomcat 和各種數(shù)據(jù)庫等。

但是,“線程池”和“連接池”技術(shù)也只是在一定程度上緩解了頻繁調(diào)用 IO 接口帶來的資源占用。而且,所謂“池”始終有其上限,當(dāng)請求大大超過上限時,“池”構(gòu)成的系統(tǒng)對外界的響應(yīng)并不比沒有池的時候效果好多少。所以使用“池” 必須考慮其面臨的響應(yīng)規(guī)模,并根據(jù)響應(yīng)規(guī)模調(diào)整“池”的大小。

對應(yīng)上例中的所面臨的可能同時出現(xiàn)的上千甚至上萬次的客戶端請求,“線程池”或“連接池”或許可以緩解部分壓力,但是不能解決所有問題。

總之,多線程模型可以方便高效的解決小規(guī)模的服務(wù)請求,但面對大規(guī)模的服務(wù)請求,多線程模型并不是***方案。下一章我們將討論用非阻塞接口來嘗試解決這個問題。
 

 

使用select()接口的基于事件驅(qū)動的服務(wù)器模型

大部分 Unix/Linux 都支持 select 函數(shù),該函數(shù)用于探測多個文件句柄的狀態(tài)變化。下面給出 select 接口的原型:
 

  1. FD_ZERO(int fd, fd_set* fds)   
  2.  FD_SET(int fd, fd_set* fds)   
  3.  FD_ISSET(int fd, fd_set* fds)   
  4.  FD_CLR(int fd, fd_set* fds)   
  5.  int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,   
  6.         struct timeval *timeout)  

這里,fd_set 類型可以簡單的理解為按 bit 位標(biāo)記句柄的隊列,例如要在某 fd_set 中標(biāo)記一個值為 16 的句柄,則該 fd_set 的第 16 個 bit 位被標(biāo)記為 1。具體的置位、驗證可使用 FD_SET、FD_ISSET 等宏實現(xiàn)。在 select() 函數(shù)中,readfds、writefds 和 exceptfds 同時作為輸入?yún)?shù)和輸出參數(shù)。如果輸入的 readfds 標(biāo)記了 16 號句柄,則 select() 將檢測 16 號句柄是否可讀。在 select() 返回后,可以通過檢查 readfds 有否標(biāo)記 16 號句柄,來判斷該“可讀”事件是否發(fā)生。另外,用戶可以設(shè)置 timeout 時間。

下面將重新模擬上例中從多個客戶端接收數(shù)據(jù)的模型。

圖4.使用select()的接收數(shù)據(jù)模型

圖 4. 使用 select() 的接收數(shù)據(jù)模型 

上述模型只是描述了使用 select() 接口同時從多個客戶端接收數(shù)據(jù)的過程;由于 select() 接口可以同時對多個句柄進(jìn)行讀狀態(tài)、寫狀態(tài)和錯誤狀態(tài)的探測,所以可以很容易構(gòu)建為多個客戶端提供獨(dú)立問答服務(wù)的服務(wù)器系統(tǒng)。

圖5.使用select()接口的基于事件驅(qū)動的服務(wù)器模型

圖 5. 使用 select() 接口的基于事件驅(qū)動的服務(wù)器模型 

這里需要指出的是,客戶端的一個 connect() 操作,將在服務(wù)器端激發(fā)一個“可讀事件”,所以 select() 也能探測來自客戶端的 connect() 行為。

上述模型中,最關(guān)鍵的地方是如何動態(tài)維護(hù) select() 的三個參數(shù) readfds、writefds 和 exceptfds。作為輸入?yún)?shù),readfds 應(yīng)該標(biāo)記所有的需要探測的“可讀事件”的句柄,其中永遠(yuǎn)包括那個探測 connect() 的那個“母”句柄;同時,writefds 和 exceptfds 應(yīng)該標(biāo)記所有需要探測的“可寫事件”和“錯誤事件”的句柄 ( 使用 FD_SET() 標(biāo)記 )。

作為輸出參數(shù),readfds、writefds 和 exceptfds 中的保存了 select() 捕捉到的所有事件的句柄值。程序員需要檢查的所有的標(biāo)記位 ( 使用 FD_ISSET() 檢查 ),以確定到底哪些句柄發(fā)生了事件。

上述模型主要模擬的是“一問一答”的服務(wù)流程,所以,如果 select() 發(fā)現(xiàn)某句柄捕捉到了“可讀事件”,服務(wù)器程序應(yīng)及時做 recv() 操作,并根據(jù)接收到的數(shù)據(jù)準(zhǔn)備好待發(fā)送數(shù)據(jù),并將對應(yīng)的句柄值加入 writefds,準(zhǔn)備下一次的“可寫事件”的 select() 探測。同樣,如果 select() 發(fā)現(xiàn)某句柄捕捉到“可寫事件”,則程序應(yīng)及時做 send() 操作,并準(zhǔn)備好下一次的“可讀事件”探測準(zhǔn)備。下圖描述的是上述模型中的一個執(zhí)行周期。

圖6. 一個執(zhí)行周期

圖 6. 一個執(zhí)行周期 

這種模型的特征在于每一個執(zhí)行周期都會探測一次或一組事件,一個特定的事件會觸發(fā)某個特定的響應(yīng)。我們可以將這種模型歸類為“事件驅(qū)動模型”。

相比其他模型,使用 select() 的事件驅(qū)動模型只用單線程(進(jìn)程)執(zhí)行,占用資源少,不消耗太多 CPU,同時能夠為多客戶端提供服務(wù)。如果試圖建立一個簡單的事件驅(qū)動的服務(wù)器程序,這個模型有一定的參考價值。

但這個模型依舊有著很多問題。

首先,select() 接口并不是實現(xiàn)“事件驅(qū)動”的***選擇。因為當(dāng)需要探測的句柄值較大時,select() 接口本身需要消耗大量時間去輪詢各個句柄。很多操作系統(tǒng)提供了更為高效的接口,如 linux 提供了 epoll,BSD 提供了 kqueue,Solaris 提供了 /dev/poll …。如果需要實現(xiàn)更高效的服務(wù)器程序,類似 epoll 這樣的接口更被推薦。遺憾的是不同的操作系統(tǒng)***的 epoll 接口有很大差異,所以使用類似于 epoll 的接口實現(xiàn)具有較好跨平臺能力的服務(wù)器會比較困難。

其次,該模型將事件探測和事件響應(yīng)夾雜在一起,一旦事件響應(yīng)的執(zhí)行體龐大,則對整個模型是災(zāi)難性的。如下例,龐大的執(zhí)行體 1 的將直接導(dǎo)致響應(yīng)事件 2 的執(zhí)行體遲遲得不到執(zhí)行,并在很大程度上降低了事件探測的及時性。

圖7. 龐大的執(zhí)行體對使用select()的事件驅(qū)動模型的影響

圖 7. 龐大的執(zhí)行體對使用 select() 的事件驅(qū)動模型的影響 

幸運(yùn)的是,有很多高效的事件驅(qū)動庫可以屏蔽上述的困難,常見的事件驅(qū)動庫有 libevent 庫,還有作為 libevent 替代者的 libev 庫。這些庫會根據(jù)操作系統(tǒng)的特點選擇最合適的事件探測接口,并且加入了信號 (signal) 等技術(shù)以支持異步響應(yīng),這使得這些庫成為構(gòu)建事件驅(qū)動模型的不二選擇。下章將介紹如何使用 libev 庫替換 select 或 epoll 接口,實現(xiàn)高效穩(wěn)定的服務(wù)器模型。

使用事件驅(qū)動庫libev的服務(wù)器模型

Libev 是一種高性能事件循環(huán) / 事件驅(qū)動庫。作為 libevent 的替代作品,其***個版本發(fā)布與 2007 年 11 月。Libev 的設(shè)計者聲稱 libev 擁有更快的速度,更小的體積,更多功能等優(yōu)勢,這些優(yōu)勢在很多測評中得到了證明。正因為其良好的性能,很多系統(tǒng)開始使用 libev 庫。本章將介紹如何使用 Libev 實現(xiàn)提供問答服務(wù)的服務(wù)器。

(事實上,現(xiàn)存的事件循環(huán) / 事件驅(qū)動庫有很多,作者也無意推薦讀者一定使用 libev 庫,而只是為了說明事件驅(qū)動模型給網(wǎng)絡(luò)服務(wù)器編程帶來的便利和好處。大部分的事件驅(qū)動庫都有著與 libev 庫相類似的接口,只要明白大致的原理,即可靈活挑選合適的庫。)

與前章的模型類似,libev 同樣需要循環(huán)探測事件是否產(chǎn)生。Libev 的循環(huán)體用 ev_loop 結(jié)構(gòu)來表達(dá),并用 ev_loop( ) 來啟動。
 

  1. void ev_loop( ev_loop* loop, int flags )  

Libev 支持八種事件類型,其中包括 IO 事件。一個 IO 事件用 ev_io 來表征,并用 ev_io_init() 函數(shù)來初始化:

  1. void ev_io_init(ev_io *io, callback, int fd, int events)  

初始化內(nèi)容包括回調(diào)函數(shù) callback,被探測的句柄 fd 和需要探測的事件,EV_READ 表“可讀事件”,EV_WRITE 表“可寫事件”。

現(xiàn)在,用戶需要做的僅僅是在合適的時候,將某些 ev_io 從 ev_loop 加入或剔除。一旦加入,下個循環(huán)即會檢查 ev_io 所指定的事件有否發(fā)生;如果該事件被探測到,則 ev_loop 會自動執(zhí)行 ev_io 的回調(diào)函數(shù) callback();如果 ev_io 被注銷,則不再檢測對應(yīng)事件。

無論某 ev_loop 啟動與否,都可以對其添加或刪除一個或多個 ev_io,添加刪除的接口是 ev_io_start() 和 ev_io_stop()。
 

  1. void ev_io_start( ev_loop *loop, ev_io* io )   
  2. void ev_io_stop( EV_A_* ) 

由此,我們可以容易得出如下的“一問一答”的服務(wù)器模型。由于沒有考慮服務(wù)器端主動終止連接機(jī)制,所以各個連接可以維持任意時間,客戶端可以自由選擇退出時機(jī)。

圖8. 使用libev庫的服務(wù)器模型

圖 8. 使用 libev 庫的服務(wù)器模型 

上述模型可以接受任意多個連接,且為各個連接提供完全獨(dú)立的問答服務(wù)。借助 libev 提供的事件循環(huán) / 事件驅(qū)動接口,上述模型有機(jī)會具備其他模型不能提供的高效率、低資源占用、穩(wěn)定性好和編寫簡單等特點。

由于傳統(tǒng)的 web 服務(wù)器,ftp 服務(wù)器及其他網(wǎng)絡(luò)應(yīng)用程序都具有“一問一答”的通訊邏輯,所以上述使用 libev 庫的“一問一答”模型對構(gòu)建類似的服務(wù)器程序具有參考價值;另外,對于需要實現(xiàn)遠(yuǎn)程監(jiān)視或遠(yuǎn)程遙控的應(yīng)用程序,上述模型同樣提供了一個可行的實現(xiàn)方案。

總結(jié)

本文圍繞如何構(gòu)建一個提供“一問一答”的服務(wù)器程序,先后討論了用阻塞型的 socket 接口實現(xiàn)的模型,使用多線程的模型,使用 select() 接口的基于事件驅(qū)動的服務(wù)器模型,直到使用 libev 事件驅(qū)動庫的服務(wù)器模型。文章對各種模型的優(yōu)缺點都做了比較,從比較中得出結(jié)論,即使用“事件驅(qū)動模型”可以的實現(xiàn)更為高效穩(wěn)定的服務(wù)器程序。文中描述的多種模型可以為讀者的網(wǎng)絡(luò)編程提供參考價值。

原文鏈接:http://blog.csdn.net/lmh12506/article/details/7753978

責(zé)任編輯:林師授 來源: lmh12506的博客
相關(guān)推薦

2014-06-26 14:10:44

2010-03-24 11:39:01

2011-02-22 11:23:48

vsFTPDLinux服務(wù)器

2011-02-22 11:23:48

vsFTPDLinux服務(wù)器

2011-07-14 14:17:33

網(wǎng)絡(luò)服務(wù)器配置DNS服務(wù)器

2011-07-14 14:45:01

網(wǎng)絡(luò)服務(wù)器配置DHCP服務(wù)器

2011-07-14 15:28:11

服務(wù)器

2011-09-05 09:23:50

2011-08-22 11:00:10

nagios

2011-08-22 11:00:14

nagios

2011-08-22 11:00:17

nagios

2012-10-25 13:57:46

2011-07-14 13:13:44

網(wǎng)絡(luò)服務(wù)器配置

2011-08-22 10:30:29

nagios

2011-03-22 13:50:53

2011-07-14 14:58:19

網(wǎng)絡(luò)服務(wù)器配置服務(wù)器

2018-08-09 09:10:54

2013-01-25 11:20:06

2021-08-13 16:05:26

僵尸網(wǎng)絡(luò)漏洞網(wǎng)絡(luò)攻擊

2011-07-14 14:01:29

網(wǎng)絡(luò)服務(wù)器配置服務(wù)器
點贊
收藏

51CTO技術(shù)棧公眾號