本文同步自游戲人生
o *__ 序 __* o
在閱讀ACE代碼和C++NPv1, v2, APG的時(shí)候,我意識(shí)到一個(gè)問(wèn)題:雖然稍有C++和網(wǎng)絡(luò)基礎(chǔ)的同學(xué)都可以讀懂ACE,但如果你對(duì)OS(五大管理模塊都包含在內(nèi))、TCP/IP、C++、Design Patterns了解越多,你就越能體會(huì)ACE為什么需要這么龐雜,雖然它不夠完美(但至少我還沒(méi)有資格來(lái)批評(píng)這一點(diǎn),我現(xiàn)在最常想做的一個(gè)動(dòng)作就是五體投地)。
而且我隱約感覺(jué)到,我現(xiàn)在所寫的很多東西在以后(對(duì)于有些人或許就是現(xiàn)在)看來(lái)會(huì)相當(dāng)不深刻、相當(dāng)不嚴(yán)謹(jǐn),但對(duì)于一段學(xué)習(xí)歷程,這個(gè)過(guò)程是必然的、必需的。
在C++NPv1中,Douglas C. Schmidt把原始socket及其API的缺陷有些妖魔化了,比如一段加上注釋、空行在內(nèi)的35行的代碼,被指出有10處錯(cuò)誤之多。這就像很多其他語(yǔ)言的倡導(dǎo)者或反傳統(tǒng)C/C++指針者在批評(píng)指針時(shí)的說(shuō)法一樣。長(zhǎng)期使用原始socket和指針的同學(xué)對(duì)此感覺(jué)很不舒服,何況socket API提供了大量錯(cuò)誤檢測(cè)的接口,至多是不夠友好罷了。你好就好了,沒(méi)必要抓住別人一頓痛批吧,『本是同根生,相煎何太急』。
雖然Solaris、Linux的很多版本及Windows對(duì)起源于Berkeley的socket API進(jìn)行了重寫,但不可否認(rèn),由于歷史原因和POSIX標(biāo)準(zhǔn)的存在,對(duì)于使用者而言,我們可以無(wú)視這些API的實(shí)現(xiàn)差異。只是一旦我們從socket通信擴(kuò)展到其他IPC通信的話,就需要正視各種I/O細(xì)節(jié)的差異了。
由于UNIX中,對(duì)于socket, file, pipe, device的大多數(shù)操作,描述符都是通用的(這一點(diǎn),OS上面講的更清楚些)。而Windows中,句柄大多不能互換(socket對(duì)于MS來(lái)說(shuō)是舶來(lái)品)。系統(tǒng)和標(biāo)準(zhǔn)的不一致導(dǎo)致地址、協(xié)議和API的混雜甚至混亂。
UNIX下的描述符和Windows的句柄可以看作是同一個(gè)概念,只是應(yīng)用環(huán)境不一樣,所描述的內(nèi)容也時(shí)常不一樣,再簡(jiǎn)單了說(shuō),它們都是一個(gè)整型的ID。
ACE的源碼中使用了大量預(yù)處理指令,尤其在跨平臺(tái)/編譯環(huán)境的部分更加明顯。鑒于C/C++標(biāo)準(zhǔn)的博大胸懷,有些指令需要閱讀相關(guān)編譯器提供的幫助文檔:
o #define (#, #@, ##) : GCC, MSVC
其中有若干代碼文件以.inl為后綴,里面是對(duì)部分函數(shù)的內(nèi)聯(lián)實(shí)現(xiàn),以使代碼結(jié)構(gòu)看上去更加簡(jiǎn)潔。如果確定使用內(nèi)聯(lián)函數(shù)的話,*.inl將被包含于*.h的最后,如果不使用,則像*.h一樣,包含于*.cpp的頭部。
ACE采用doxygen輸出文檔,在閱讀代碼注釋時(shí)能夠感受到差異,但基本不會(huì)影響閱讀。
o * __ 關(guān)于第3章(C++NPv1)__ * o
ACE抽象的地址類ACE_Addr擁有ACE_DEV_Addr, ACE_FILE_Addr, ACE_INET_Addr, ACE_SPIPE_Addr, ACE_UNIX_Addr五個(gè)子類。對(duì)于狹義上的網(wǎng)絡(luò)通信(TCP/IP)而言,ACE_INET_Addr對(duì)應(yīng)于我們熟悉的sockaddr_in。
ACE_IPC_SAP是IPC(interprocess communication)I/O操作類的root類。
從編碼的角度看,這個(gè)類漂亮的地方在于示例了抽象類的另一種實(shí)現(xiàn)方式。
一提到抽象類,大多數(shù)人的第一反應(yīng)是pure virtual function。當(dāng)一個(gè)基類確定需要使用virtual function時(shí),這是一個(gè)不錯(cuò)的選擇。但我們都知道虛擬函數(shù)有開(kāi)銷。而且對(duì)于一個(gè)結(jié)構(gòu)簡(jiǎn)單的抽象基類和其繼承子類(尤其是大量使用時(shí)),一個(gè)虛函數(shù)表帶來(lái)的開(kāi)銷會(huì)讓整個(gè)設(shè)計(jì)顯得十分蹩腳。
我們都知道如何強(qiáng)制讓一個(gè)類無(wú)法使用default constructor(protected)。如果對(duì)基類使用該方法,僅使子類具有public的default constructor,這就達(dá)到了定義抽象基類的效果。
virtual destructor的意義在于防止delete父類指針(指向子類對(duì)象)時(shí)未調(diào)用子類destructor。在此例中,為避免這種情況,同樣將destructor聲明為protected即可。
從設(shè)計(jì)實(shí)現(xiàn)的角度看,相較于socket API,ACE_IPC_SAP的子類ACE_SOCK提供了編譯時(shí)對(duì)句柄合法性的檢測(cè)。
從邏輯功能層面劃分,socket有三種角色:
o active connection role (connector):主動(dòng)連接
o passive connection role (acceptor):被動(dòng)連接
o communication role (stream):數(shù)據(jù)通信
但socket API畢竟不是OOD出來(lái)的,對(duì)于一個(gè)socket描述符,也完全沒(méi)有必要去限制其擔(dān)負(fù)的功能,更不可能搞成三種不同的socket。而OOD的ACE則可以輕易實(shí)現(xiàn)對(duì)socket對(duì)象及其操作的封裝。
工廠類ACE_SOCK_Connector是一個(gè)主動(dòng)創(chuàng)建通信端的工廠類。socket API中的connect接口只是為一個(gè)socket建立與其它peer的網(wǎng)絡(luò)連接,而不產(chǎn)生新的socket實(shí)例,也不依賴于任何其它socket。同樣,ACE_SOCK_Connector只是為一個(gè)ACE_SOCK_Stream對(duì)象(對(duì)用于數(shù)據(jù)通信的socket的封裝)連接到ACE_Addr(對(duì)struct sockaddr的封裝)提供接口,也不含對(duì)ACE_SOCK_Stream對(duì)象的其它操作。
工廠類ACE_SOCK_Acceptor是一個(gè)被動(dòng)創(chuàng)建通信端的工廠類。當(dāng)監(jiān)聽(tīng)到新的網(wǎng)絡(luò)連接后,為該連接初始化一個(gè)ACE_SOCK_Stream對(duì)象。和connector不同的是,acceptor依賴于一個(gè)已經(jīng)存在的充當(dāng)監(jiān)聽(tīng)功能的socket句柄(ACE_SOCK),因此,ACE_SOCK_Acceptor是ACE_SOCK的一個(gè)子類。
ACE_SOCK_Stream是只負(fù)有通信傳輸功能的socket,對(duì)應(yīng)connection-oriented的TCP通信格式stream,和UDP的CE_SOCK_CODgram相呼應(yīng)。ACE_SOCK_Stream只是socket的通信載體,在兩個(gè)工廠ACE_SOCK_Connector和ACE_SOCK_Acceptor中初始化。這樣一個(gè)類除支持最基本的數(shù)據(jù)發(fā)送(send)和接收(recv)和阻塞(blocking)、非阻塞(nonblocking)及定時(shí)(timed)的I/O模式外,還支持分散讀取(scatter-read)和集中寫入(gather-write)。
對(duì)于一個(gè)簡(jiǎn)單的『網(wǎng)絡(luò)課程作業(yè):寫一個(gè)有連接的IM小程序』,上面這些內(nèi)容已經(jīng)足夠了。當(dāng)然即使使用對(duì)應(yīng)的幾個(gè)socket API也已經(jīng)足夠了。但我們顯然更加關(guān)心如此龐大的一個(gè)庫(kù),是如何解決復(fù)雜的網(wǎng)絡(luò)應(yīng)用的,我尤其關(guān)心的是多線程并發(fā)如何更好的處理。
所以,我準(zhǔn)備跑到第8、9章了。