spserver 是一個實現了半同步/半異步(Half-Sync/Half-Async)和領導者/追隨者(Leader/Follower) 模式的服務器框架,能夠簡化 TCP server 的開發工作。
spserver 使用 c++ 實現,目前實現了以下功能:
1.封裝了 TCP server 中接受連接的功能;
2.使用非阻塞型I/O和事件驅動模型,由主線程負責處理所有 TCP 連接上的數據讀取和發送,因此連接數不受線程數的限制;
3.主線程讀取到的數據放入隊列,由一個線程池處理實際的業務。
4.一個 http 服務器框架,即嵌入式 web 服務器(請參考:
SPWebServer:一個基于 SPServer 的 web 服務器框架
)
0.6 版本之前只包含 Half-Sync/Half-Async 模式的實現,0.6 版本開始包含 Leader/Follower 模式的實現
0.7 版本開始支持 ssl 。把 socket 相關的操作抽象為一個 IOChannel 層,關于 openssl 的部分單獨實現為一個 plugin 的形式,對于不使用 ssl 功能的用戶,不需要引入 ssl 相關的頭文件和庫。
0.7.5 增加了一個 sptunnel 程序,是一個通用的 ssl proxy 。類似 stunnel 。
0.9.0 移植 spserver 到 windows 平臺,需要在 windows 下編譯 libevent 和 pthread 。
0.9.1 在 windows 平臺,去掉了對 libevent 和 pthread 依賴,完全使用 iocp 和 windows 的線程機制實現了半同步半異步的框架。
0.9.2 移植了所有的功能到 windows 平臺,同時新增加了 xyssl 的插件。
主頁:
http://code.google.com/p/spserver/源代碼下載:
http://spserver.googlecode.com/files/spserver-0.6.src.tar.gzhttp://code.google.com/p/spserver/downloads/list在實現并發處理多事件的應用程序方面,有如下兩種常見的編程模型:
ThreadPerConnection的多線程模型和事件驅動的單線程模型。
ThreadPerConnection的多線程模型優點:簡單易用,效率也不錯。在這種模型中,開發者使用同步操作來編寫程序,比如使用阻塞型I/O。使用同步操作的程序能夠隱式地在線程的運行堆棧中維護應用程序的狀態信息和執行歷史,方便程序的開發。
缺點:沒有足夠的擴展性。如果應用程序只需處理少量的并發連接,那么對應地創建相應數量的線程,一般的機器都還能勝任;但如果應用程序需要處理成千上萬個連接,那么為每個連接創建一個線程也許是不可行的。
事件驅動的單線程模型優點:擴展性高,通常性能也比較好。在這種模型中,把會導致阻塞的操作轉化為一個異步操作,主線程負責發起這個異步操作,并處理這個異步操作的結果。由于所有阻塞的操作都轉化為異步操作,理論上主線程的大部分時間都是在處理實際的計算任務,少了多線程的調度時間,所以這種模型的性能通常會比較好。
缺點:要把所有會導致阻塞的操作轉化為異步操作。一個是帶來編程上的復雜度,異步操作需要由開發者來顯示地管理應用程序的狀態信息和執行歷史。第二個是目前很多廣泛使用的函數庫都很難轉為用異步操作來實現,即是可以用異步操作來實現,也將進一步增加編程的復雜度。
并發系統通常既包含異步處理服務,又包含同步處理服務。系統程序員有充分的理由使用異步特性改善性能。相反,應用程序員也有充分的理由使用同步處理簡化他們的編程強度。針對這種情況,ACE 的作者提出了 半同步/半異步 (Half-Sync/Half-Async) 模式。引用
《POSA2》上對這個模式的描述如下:
半同步/半異步 體系結構模式將并發系統中的異步和同步服務處理分離,簡化了編程,同時又沒有降低性能。該模式介紹了兩個通信層,一個用于異步服務處理,另一個用于同步服務處理。
目標:需要同步處理的簡易性的應用程序開發者無需考慮異步的復雜性。同時,必須將性能最大化的系統開發者不需要考慮同步處理的低效性。讓同步和異步處理服務能夠相互通信,而不會使它們的編程模型復雜化或者過度地降低它們的性能。
解決方案:將系統中的服務分解成兩層,同步和異步,并且在它們之間增加一個排隊層協調異步和同步層中的服務之間的通信。在獨立線程或進程中同步地處理高層服務(如耗時長的數據庫查詢或文件傳輸),從而簡化并發編程。相反,異步地處理底層服務(如從網絡連接上讀取數據),以增強性能。如果駐留在相互獨立的同步和異步層中的服務必須相互通信或同步它們的處理,則應允許它們通過一個排隊層向對方傳遞消息。
模式原文:Half-Sync/Half-Async: An Architectural Pattern for Efficient and Well-structured Concurrent I/O
http://www.cs.wustl.edu/~schmidt/PDF/HS-HA.pdf中文翻譯:
http://blog.chinaunix.net/u/31756/showart_245841.html如果上面關于 半同步/半異步 的說明過于抽象,那么可以看一個《POSA2》中提到的例子:
許多餐廳使用 半同步/半異步 模式的變體。例如,餐廳常常雇傭一個領班負責迎接顧客,并在餐廳繁忙時留意給顧客安排桌位,為等待就餐的顧客按序排隊是必要的。領班由所有顧客“共享”,不能被任何特定顧客占用太多時間。當顧客在一張桌子入坐后,有一個侍應生專門為這張桌子服務。
下面來看一個使用 spserver 實現的簡單的 line echo server 。- class?SP_EchoHandler?:?public?SP_Handler?{ ??
- public: ??
- ??SP_EchoHandler(){} ??
- ??virtual?~SP_EchoHandler(){} ??
- ??
- ????
- ??virtual?int?start(?SP_Request?*?request,?SP_Response?*?response?)?{ ??
- ????request->setMsgDecoder(?new?SP_LineMsgDecoder()?); ??
- ????response->getReply()->getMsg()->append( ??
- ??????"Welcome?to?line?echo?server,?enter?'quit'?to?quit.\r\n"?); ??
- ??
- ????return?0;??? ??
- ??}????? ??
- ??
- ????
- ??virtual?int?handle(?SP_Request?*?request,?SP_Response?*?response?)?{ ??
- ????SP_LineMsgDecoder?*?decoder?=?(SP_LineMsgDecoder*)request->getMsgDecoder(); ??
- ??
- ????if(?0?!=?strcasecmp(?(char*)decoder->getMsg(),?"quit"?)?)?{ ??
- ??????response->getReply()->getMsg()->append(?(char*)decoder->getMsg()?); ??
- ??????response->getReply()->getMsg()->append(?"\r\n"?); ??
- ??????return?0;????????? ??
- ????}?else?{???? ??
- ??????response->getReply()->getMsg()->append(?"Byebye\r\n"?); ??
- ??????return?-1;???????? ??
- ????}??????????? ??
- ??}????? ??
- ??virtual?void?error(?SP_Response?*?response?)?{} ??
- ??
- ??virtual?void?timeout(?SP_Response?*?response?)?{} ??
- ??
- ??virtual?void?close()?{} ??
- }; ??
- ??
- class?SP_EchoHandlerFactory?:?public?SP_HandlerFactory?{ ??
- public: ??
- ??SP_EchoHandlerFactory()?{} ??
- ??virtual?~SP_EchoHandlerFactory()?{} ??
- ??
- ??virtual?SP_Handler?*?create()?const?{ ??
- ????return?new?SP_EchoHandler(); ??
- ??} ??
- }; ??
- ??
- int?main(?int?argc,?char?*?argv[]?) ??
- { ??
- ??int?port?=?3333; ??
- ??
- ??SP_Server?server(?"",?port,?new?SP_EchoHandlerFactory()?); ??
- ??server.runForever(); ??
- ??
- ??return?0; ??
- }??
class SP_EchoHandler : public SP_Handler {
public:
SP_EchoHandler(){}
virtual ~SP_EchoHandler(){}
// return -1 : terminate session, 0 : continue
virtual int start( SP_Request * request, SP_Response * response ) {
request->setMsgDecoder( new SP_LineMsgDecoder() );
response->getReply()->getMsg()->append(
"Welcome to line echo server, enter 'quit' to quit.\r\n" );
return 0;
}
// return -1 : terminate session, 0 : continue
virtual int handle( SP_Request * request, SP_Response * response ) {
SP_LineMsgDecoder * decoder = (SP_LineMsgDecoder*)request->getMsgDecoder();
if( 0 != strcasecmp( (char*)decoder->getMsg(), "quit" ) ) {
response->getReply()->getMsg()->append( (char*)decoder->getMsg() );
response->getReply()->getMsg()->append( "\r\n" );
return 0;
} else {
response->getReply()->getMsg()->append( "Byebye\r\n" );
return -1;
}
}
virtual void error( SP_Response * response ) {}
virtual void timeout( SP_Response * response ) {}
virtual void close() {}
};
class SP_EchoHandlerFactory : public SP_HandlerFactory {
public:
SP_EchoHandlerFactory() {}
virtual ~SP_EchoHandlerFactory() {}
virtual SP_Handler * create() const {
return new SP_EchoHandler();
}
};
int main( int argc, char * argv[] )
{
int port = 3333;
SP_Server server( "", port, new SP_EchoHandlerFactory() );
server.runForever();
return 0;
}
在最簡單的情況下,使用 spserver 實現一個 TCP server 需要實現兩個類:SP_Handler 的子類 和 SP_HandlerFactory 的子類。
SP_Handler 的子類負責處理具體業務。
SP_HandlerFactory 的子類協助 spserver 為每一個連接創建一個 SP_Handler 子類實例。
1.SP_Handler 生命周期SP_Handler 和 TCP 連接一對一,SP_Handler 的生存周期和 TCP 連接一樣。
當 TCP 連接被接受之后,SP_Handler 被創建,當 TCP 連接斷開之后,SP_Handler將被 destroy。
2.SP_Handler 函數說明SP_Handler 有 5 個純虛方法需要由子類來重載。這 5 個方法分別是:
start:當一個連接成功接受之后,將首先被調用。返回 0 表示繼續,-1 表示結束連接。
handle:當一個請求包接收完之后,將被調用。返回 0 表示繼續,-1 表示結束連接。
error:當在一個連接上讀取或者發送數據出錯時,將被調用。error 之后,連接將被關閉。
timeout:當一個連接在約定的時間內沒有發生可讀或者可寫事件,將被調用。timeout 之后,連接將被關閉。
close:當一個 TCP 連接被關閉時,無論是正常關閉,還是因為 error/timeout 而關閉。
3.SP_Handler 函數調用時機當需要調用 SP_Handler 的 start/handle/error/timeout 方法時,相關的參數將被放入隊列,然后由線程池來負責執行 SP_Handler 對應的方法。因此在 start/handle/error/timeout 中可以使用同步操作來編程,可以直接使用阻塞型 I/O 。
在發生 error 和 timeout 事件之后,close 緊跟著這兩個方法之后被調用。
如果是程序正常指示結束連接,那么在主線程中直接調用 close 方法。
4.高級功能--MsgDecoder 這個 line echo server 比起常見的 echo server 有一點不同:只有在讀到一行時才進行 echo。
這個功能是通過一個稱為 MsgDecoder 的接口來實現的。不同的 TCP server 在應用層的傳輸格式上各不相同。
比如在 SMTP/POP 這一類的協議中,大部分命令是使用 CRLF 作為分隔符的。而在 HTTP 中是使用 Header + Body 的形式。
為了適應不同的 TCP server,在 spserver 中有一個 MsgDecoder 接口,用來處理這些應用層的協議。
比如在這個 line echo server 中,把傳輸協議定義為:只有讀到一行時將進行 echo 。
那么相應地就要實現一個 SP_LineMsgDecoder ,這個 LineMsgDecoder 負責判斷目前的輸入緩沖區中是否已經有完整的一行。
MsgDecoder 的接口如下:
- class?SP_MsgDecoder?{ ??
- public: ??
- ??virtual?~SP_MsgDecoder(); ??
- ??
- ??enum?{?eOK,?eMoreData?}; ??
- ??virtual?int?decode(?SP_Buffer?*?inBuffer?)?=?0; ??
- };??
class SP_MsgDecoder {
public:
virtual ~SP_MsgDecoder();
enum { eOK, eMoreData };
virtual int decode( SP_Buffer * inBuffer ) = 0;
};
decode 方法對 inBuffer 里面的數據進行檢查,看是否符合特定的要求。如果已經符合要求,那么返回 eOK ;如果還不滿足要求,那么返回 eMoreData。比如 LineMsgDecoder 的 decode 方法的實現為:
- int?SP_LineMsgDecoder?::?decode(?SP_Buffer?*?inBuffer?) ??
- {??????????????? ??
- ??if(?NULL?!=?mLine?)?free(?mLine?); ??
- ??mLine?=?inBuffer->getLine(); ??
- ???????? ??
- ??return?NULL?==?mLine???eMoreData?:?eOK; ??
- }?????
int SP_LineMsgDecoder :: decode( SP_Buffer * inBuffer )
{
if( NULL != mLine ) free( mLine );
mLine = inBuffer->getLine();
return NULL == mLine ? eMoreData : eOK;
}
spserver 默認提供了幾個 MsgDecoder 的實現:
SP_DefaultMsgDecoder :它的 decode 總是返回 eOK ,即只要有輸入就當作是符合要求了。
??? 如果應用不設置 SP_Request->setMsgDecoder 的話,默認使用這個。
SP_LineMsgDecoder : 檢查到有一行的時候,返回 eOK ,按行讀取輸入。
SP_DotTermMsgDecoder :檢查到輸入中包含了特定的 <CRLF>.<CRLF> 時,返回 eOK。
具體的使用例子可以參考示例:testsmtp 。
5.高級功能--實現聊天室 spserver 還提供了一個廣播消息的功能。使用消息廣播功能可以方便地實現類似聊天室的功能。具體的實現可以參考示例:testchat 。
6.libeventspserver 使用 c++ 實現,使用了一個第三方庫--libevent,以便在不同的平臺上都能夠使用最有效的事件驅動機制(Currently, libevent supports /dev/poll, kqueue(2), select(2), poll(2) and epoll(4). )。