網絡編程的模式分爲3種:
1. nginx的全異步方式,使用epoll處理網絡數據,對於請求的處理也完全是異步的。任何一個請求的處理如果花費了較長時間,那麼nginx進程就會被處理操作阻塞,導致無法處理IO事件
2. 簡單的一個連接一個線程方案,這種方案無法處理大量併發的連接,適用mysql這類連接數不多的場景。其中也有一些優化的做法,例如使用線程池避免不斷的創建銷燬線程。
3. 半同步半異步方式,啓動一個IO線程使用epoll處理網絡數據。當收到一個完整的請求包,把請求放到任務隊列,有一個線程池不斷的從隊列裏獲取任務,同步處理,處理完之後再把響應數據由IO線程返回給用戶。
其中半同步半異步方式廣泛應用於服務器端編程,例如taobao開源的tbnet(tair,tfs都使用該庫)。主要原因是nginx的全異步方式編寫程序難度高,開發效率低,而一個連接一個線程方案無法支持大量的併發連接。半同步半異步方式則是開發效率與高性能之間的一個權衡,網絡處理等有框架進行處理,開發者只關注業務邏輯的編寫,只需同步處理請求即可,對於需要訪問mysql等的應用非常方便。
對於tbnet這類的半同步半異步的網絡庫,使用者需要對多個類進行繼承,override相關的handler來處理,一個非常簡單的例子需要不少代碼。
c++11標準出來之後,處理函數可以用匿名函數優雅的解決。handy是一個使用最新的c++11來簡化網絡編程的庫,裏面的例子非常簡潔。
使用handy庫進行半同步半異步編程的例子如下:
#include <handy/handy.h>
using namespace std;
using namespace handy;
int main(int argc, const char* argv[]) {
EventBase base;
HSHA hsha(&base, 4);
int r = hsha.bind("", 99);
exitif(r, "bind failed");
Signal::signal(SIGINT, [&]{ base.exit(); hsha.exit(); signal(SIGINT, SIG_DFL);});
hsha.onMsg(new LineCodec, [](const TcpConnPtr& con, const string& input){
int ms = rand() % 1000;
info("processing a msg");
usleep(ms * 1000);
return util::format("%s used %d ms", input.c_str(), ms);
});
base.loop();
info("program exited");
}
其中hsha.onMsg調用指定如何處理消息,第一個參數指定如何對消息進行編解碼,這裏傳入的是一個行解碼器,適用於telnet中的應用。第二個參數是個回調函數,指定如何處理消息。
回調函數第一個參數爲連接指針,第二個參數爲消息。返回值爲string,即消息的響應。
HSHA hsha(&base, 4) 創建一個半同步半異步服務器,線程池中線程數量爲4.