boost.asio包裝類st_asio_wrapper開發教程(轉)

weixin_34119545發表於2013-10-28

一:什麼是st_asio_wrapper
它是一個c/s網路程式設計框架,基於對boost.asio的包裝(最低在boost-1.49.0上除錯過),目的是快速的構建一個c/s系統;

二:st_asio_wrapper的特點
效率高、跨平臺、完全非同步,當然這是從boost.asio繼承而來;
自動重連,資料透明傳輸,自動解決分包粘包問題(這一特性表現得與udp一樣);
只支援tcp和udp協議;

三:st_asio_wrapper的大體結構
st_asio_wrapper.h:
編譯器版本測試,更新日誌等;

st_asio_wrapper_base.h:
存放一些全域性類、函式、巨集以及日誌輸出等;

st_asio_wrapper_timer.h(class st_timer):
定時器類,以下類均需要,除了打包解包器;

st_asio_wrapper_socket.h(class st_socket):
st_tcp_socket和st_udp_socket的基類,主要負責訊息派發相關功能;

st_asio_wrapper_tcp_socket.h(class st_tcp_socket):
tcp套接字類,用於tcp資料的收發,從st_socket繼承;

st_asio_wrapper_udp_socket.h(class st_udp_socket):
udp套接字類,用於udp資料的收發,從st_socket繼承;

st_asio_wrapper_packer.h(interface i_packer, class packer):
i_packer是打包器的介面,packer是st_asio_wrapper自帶的打包器;

st_asio_wrapper_unpacker.h(interface i_unpacker, class unpacker):
i_unpacker是解包器的介面,unpacker是st_asio_wrapper自帶的解包器;

st_asio_wrapper_service_pump.h(interface i_service, class st_service_pump):
asio的io_servie包裝類,用於執行st_asio_wrapper的所有service物件(st_server_base, st_sclient, st_tcp_client_base, st_udp_client_base);

st_asio_wrapper_object_pool.h(class st_object_pool):
物件池,從原來的st_server_base抽象出來,供st_server_base和st_client繼承,於是乎,服務端和客戶端(支援多條連線的)都有了物件池功能;

st_asio_wrapper_server.h(class st_server_base):
st_server是服務端的服務物件類,用於服務端的監聽、接受客戶端請求等,需要實現i_server介面;

st_asio_wrapper_server_socket.h(interface i_server, class st_server_socket):
前者用於從st_server獲取必要的資訊,和呼叫必要的介面;後者從st_tcp_socket繼承,用於服務端的資料收發;

st_asio_wrapper_connector.h(class st_connector):
從st_tcp_socket繼承,實現連線伺服器(包括重連)邏輯;

st_asio_wrapper_client.h(class st_sclient_base, class st_client_base):
client相關的公共邏輯,比如遍歷等,被st_tcp_client_base和st_udp_client_base繼承;

st_asio_wrapper_tcp_client.h(class st_tcp_sclient, class st_tcp_client_base):
前者從st_connector繼承,只支援一條連線,後者支援任意多條連線,他們實現了一些邏輯,以便被st_service_pump呼叫;

st_asio_wrapper_udp_client.h(class st_udp_sclient, class st_udp_client_base):
基於udp套接字的service物件;

原始碼及例程下載地址:
命令列:svn checkout http://st-asio-wrapper.googlecode.com/svn/trunk/ st-asio-wrapper-read-only
如果從svn客戶端介面上開啟,則只輸入http://st-asio-wrapper.googlecode.com/svn/trunk/到位址列即可
git:https://github.com/youngwolf-project/st_asio_wrapper/,另外,我的資源裡面也有下載,但不是最新的。
QQ交流群:198941541
其中include資料夾裡面是st_asio_wrapper的所有類、asio_client和asio_server配合用於演示最簡單的資料收發、file_server和file_client用於演示檔案傳送、test_client和asio_server配合用於效能測試、udp_client演示udp通訊,他們都基於st_asio_wrapper,可以看成是sample;

        類庫(包括demo)在vc下有幾個警告,均可安全忽略;
        類庫裡面大量使用了c++0x特性,主要有:range-based for loop、lambda表示式、nullptr、auto、右值引用、泛型begin()和end()等;因此至少需要vc2010及其以上版本的編譯器,或者gcc4.6以上;
        在vc2010和gcc4.6之前的編譯器版本中,請使用相容版本,在compatiable_edition資料夾裡面(注意相容版本的效率並不比普通版本低,甚至略高);
        推薦在能用的情況下,還是用普通版本(或者說標準版本,這是相對於相容版本而言的),雖然效率沒有提高,但你用在一個複雜的工程中時,可能普通版本效率會高,因為相同的程式碼下,c++0x的效率要普遍的高一些,在st_asio_wrapper裡面沒有表現出來,是因為我特別的對相容版本做過優化(而且我的使用也有限,比如有些無法優化的地方,我剛好不需要使用,就躲過去了);
        需要開發者自己編譯boost庫,大概需要boost.system和boost.thread兩個庫。

五:開發教程(客戶端)
客戶端直接#include st_asio_wrapper_client.h,就可實現一個簡單的客戶端了,如下:

  1. //configuration  
  2. #define SERVER_PORT     9527  
  3. #define FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer  
  4. //configuration  
  5.   
  6. #include "st_asio_wrapper_client.h"  
  7. using namespace st_asio_wrapper;  
  8.   
  9. #define QUIT_COMMAND    "quit"  
  10. #define RESTART_COMMAND "restart"  
  11. #define RECONNECT_COMMAND "reconnect"  
  12. #define SUSPEND_COMMAND "suspend"  
  13. #define RESUME_COMMAND  "resume"  
  14.   
  15. int main() {  
  16.     std::string str;  
  17.     st_service_pump service_pump;  
  18.     st_sclient client(service_pump);  
  19.     //there is no corresponding echo client demo as server endpoint  
  20.     //because echo server with echo client made dead loop, and occupy almost all the network resource  
  21.   
  22.     client.set_server_addr(SERVER_PORT + 100, SERVER_IP);  
  23. //  client.set_server_addr(SERVER_PORT, "::1"); //ipv6  
  24. //  client.set_server_addr(SERVER_PORT, "127.0.0.1"); //ipv4  
  25.   
  26.     service_pump.start_service(1);  
  27.     while(service_pump.is_running())  
  28.     {  
  29.         std::cin >> str;  
  30.         if (str == QUIT_COMMAND)  
  31.             service_pump.stop_service();  
  32.         else if (str == RESTART_COMMAND)  
  33.         {  
  34.             service_pump.stop_service();  
  35.             service_pump.start_service(1);  
  36.         }  
  37.         else if (str == RECONNECT_COMMAND)  
  38.             client.graceful_close(true);  
  39.         //the following two commands demonstrate how to suspend msg sending, no matter recv buffer been used or not  
  40.         else if (str == SUSPEND_COMMAND)  
  41.             client.suspend_send_msg(true);  
  42.         else if (str == RESUME_COMMAND)  
  43.             client.suspend_send_msg(false);  
  44.         else  
  45.             client.safe_send_msg(str);  
  46.     }  
  47. }  
  48. //restore configuration  
  49. #undef SERVER_PORT  
  50. #undef FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer  
  51. //restore configuration  

以上例子中,客戶端從控制檯接收資料,呼叫safe_send_msg傳送資料;當收到資料時,會輸出到控制檯(st_tcp_socket或者st_udp_socket實現);

        其中,start_service開啟服務,stop_service結束服務(退出時必須明確呼叫),is_running判斷服務的執行狀態;如果想修改服務端地址,則在呼叫start_service之前呼叫set_server_addr函式;
        stop_service之後,可再次呼叫start_service開啟服務;
        不stop_service而直接想重連線,則以true呼叫st_connector的force_close或者graceful_close;
        當然,一般來說,客戶端不會只把收到的資料顯示到控制檯這麼簡單,此時需要從st_tcp_sclient或者st_udp_sclient派生一個類(如果你有多條連線,則從st_connector或者st_udp_socket派生一個類,並用st_tcp_client或者st_udp_client來管理它,這個請參考test_client這個demo,它支援多條連線),然後有兩種方法供你選擇,一:重寫on_msg並在裡面直接返回true(或者定義FORCE_TO_USE_MSG_RECV_BUFFER巨集,至於為什麼要這樣,你需要看完所有st_asio_wrapper的一系列教程共四篇),然後再重寫on_msg_handle並在裡面做訊息處理;二:重寫on_msg,並在裡面做訊息處理,然後返回false。這兩種方式的區別是:前者不會阻塞訊息的接收,後者會,比如你的訊息處理需要1秒,那麼在這1秒鐘之內,前者與接收訊息並行,後者則不能接受訊息,直到訊息處理結束。另一個區別是:前者需要接收快取,後者不需要。當然,這裡說的阻塞,是指對當前套接字的阻塞,與其它套接字無關,比如在服務端,有兩個套接字,當你處理套接字1的訊息時,無論你用前面的哪一種方法,都不可能阻塞套接字2的訊息接收。關於這個話題,請參看教程第四篇
        在訊息傳送成功之後,你有一次機會得到通知,那就是重寫on_msg_send函式,它的引數就是這個訊息(注意,是打包後的);
        每呼叫一次send_msg或者safe_send_msg,對方必定收到一次一模一樣的資料,二次開發者不用再考慮分包粘包問題,這一特點你完全可以把它當成udp的行為;
        st_socket裡面有傳送訊息快取,所以當二次開發者有資料需要傳送的時候,可以隨時呼叫send_msg(注意快取滿的問題,safe_send_msg保證資料傳送成功,當快取滿時會等待)而不用關心當前連線是否已經建立,它會在條件適當的時候,為你傳送資料。

你可能需要修改的巨集有以下幾類:
1.全域性巨集,服務端客戶端均需要:
        UNIFIED_OUT_BUF_NUM:unified_out類使用的快取空間大小,預設2048;
        MAX_MSG_LEN:訊息最大長度(打包後的,拿預設的packer來說,它最大僅支援3998訊息長度,因為還有一個2位元組的包頭),預設4000。對於預設打包器解包器,這個長度範圍只能是1至(65535-2),因為包頭只用了兩個位元組的緣故,如果想要超過這個限制,只能重寫打包解包器(如果僅僅是擴充套件包頭長度,這個重寫將非常簡單);
        MAX_MSG_NUM:每個st_socket訊息快取最大訊息條數(包括訊息接收和傳送快取),預設1024;
        ENHANCED_STABILITY:增強的健壯性,如果開啟這個巨集,所有service物件都會在最外層(io_service.run)包一層try catch,以增加健壯性,當然,增加try是會有效率損失的,本巨集預設不開啟;
        FORCE_TO_USE_MSG_RECV_BUFFER:始終使用訊息接收快取,這個是編譯期優化,前面說了,on_msg()返回true代表使用訊息接收快取,如果你的on_msg()永遠返回true,則可以開啟這個巨集,那麼st_tcp_socket或者st_udp_socket將不再呼叫on_msg()(根本不存在這個虛擬函式了)而是直接將訊息放入訊息接收快取,這樣可以提高一些效率,預設不開啟本巨集;
        NO_UNIFIED_OUT:讓st_asio_wrapper裡面的所有輸出失效;
        ST_SERVICE_THREAD_NUM:IO執行緒數量,用於執行io_service.run函式。
        MAX_OBJECT_MAX:物件池最多支援的物件數量,預設4096;
        REUSE_OBJECT:是否開啟物件池,如果開啟,當建立新物件時,將嘗試使用已經被關閉的物件,此時將不會自動定時的釋放被關閉的物件連結串列,請參看SOCKET_FREE_INTERVAL巨集,預設不開啟本巨集;
        SOCKET_FREE_INTERVAL:說這個巨集之前,要說一下st_object_pool的內部工作原理:當呼叫del_object的時候(st_server_socket在on_recv_error裡面的預設行為),st_object_pool的作法並不是刪除這個物件,而是把這個物件移動到另外一個連結串列裡面,這個連結串列裡面的物件會被每SOCKET_FREE_INTERVAL秒遍歷一次,以找到已經關閉超過CLOSED_SOCKET_MAX_DURATION秒鐘的物件,然後真正的從記憶體中釋放它,原因是當del_object的時候,可能還有其它的非同步操作還沒完成,或者完成了,但還在佇列中沒有被分發,如果此時從記憶體中釋放物件,那麼後面的非同步回撥的時候,可能會出現記憶體訪問越界。這個問題可以通過為每一個非同步呼叫都繫結一個指向本物件this指標的shared_ptr做為引數,但過度使用shared_ptr會影響到效率。單位為秒,預設10秒。如果開啟了REUSE_OBJECT,則本巨集根本不使用(也就不存在定時遍歷了),因為被關閉的物件已經放入物件池,等著被重用,不能釋放;
        AUTO_CLEAR_CLOSED_SOCKET:服務端是否定時的迴圈的呼叫clear_all_closed_object()以清除已經被關閉的套接字,如果你不方便呼叫del_object,則物件池裡面將會累積越來越多的被關閉了的物件(如果有連線出錯,或者退出的話),你可以開啟這個巨集,讓st_object_pool為你定時的做這些清除工作;注意,如果物件連結串列非常大,遍歷連結串列是會影響效率的;遍歷的時間間隔由CLEAR_CLOSED_SOCKET_INTERVAL定義,預設不開啟本巨集;
        CLEAR_CLOSED_SOCKET_INTERVAL:定時呼叫clear_all_closed_object()間隔,單位為秒,預設60秒,如果未開啟AUTO_CLEAR_CLOSED_SOCKET,則本巨集根本不使用(也就不存在定時呼叫clear_all_closed_object()了);
        CLOSED_SOCKET_MAX_DURATION:關閉連線多少秒鐘之後,可以安全釋放物件或者重用物件(取決於REUSE_OBJECT是否被定義,定義了就是重用,否則釋放),預設為5秒。

2.tcp客戶端專用巨集:
        GRACEFUL_CLOSE_MAX_DURATION:優雅關閉地,最長等待時間,單位為秒,預設5。
        SERVER_IP:伺服器IP地址,用字串形式表示,預設"127.0.0.1";
        SERVER_PORT:伺服器埠,預設5050;
        RE_CONNECT_INTERVAL:當連線伺服器失敗時,延時多長時間重連,單位是毫秒,預設500毫秒;
        RE_CONNECT_CONTROL:開啟此巨集之後,可以控制重連線次數,執行時也可修改。

3.tcp服務端專用巨集:
        SERVER_PORT:伺服器埠(伺服器IP如果要設定的話,只能呼叫set_server_addr介面,不能通過巨集來實現),預設5050;
        TCP_DEFAULT_IP_VERSION:在不指定服務端IP時,通過這個巨集指定IP協議的版本(v4還是v6,取值分別是tcp::v4()和tcp::v6()),如果指定了IP,則版本從ip地址中分析得來;
        ASYNC_ACCEPT_NUM:最多同時投遞多少個非同步accept呼叫,預設1;
        NOT_REUSE_ADDRESS:關閉埠重用,udp也用使用此巨集

4.udp客戶端專用巨集:
        UDP_DEFAULT_IP_VERSION:在不指定IP時,通過這個巨集指定IP協議的版本(v4還是v6,取值分別是udp::v4()和udp::v6()),如果指定了IP,則版本從ip地址中分析得來;

以上巨集都可以按工程為單位來修改,你只需要在include相應st_asio_wrapper相關標頭檔案之前,定義這些巨集即可,具體例子demo裡面都有。

相關文章