在所有的預(yù)處理指令中,#pragma指令可能是最復(fù)雜的了,它的作用是設(shè)定編譯器的狀態(tài)或者是指示編譯器完成一些特定的動(dòng)作。#pragma指令對(duì)每個(gè) 編譯器給出了一個(gè)方法,在保持與C和C++語言完全兼容的情況下,給出主機(jī)或操作系統(tǒng)專有的特征。依據(jù)定義,編譯指示是機(jī)器或操作系統(tǒng)專有的,且對(duì)于每個(gè) 編譯器都是不同的。其格式一般為:#pragma para,其中para為參數(shù),下面來看一些常用的參數(shù)。
(Each implementation of C and C++ supports some features unique to its host machine or operating system. Some programs, for instance, need to exercise precise control over the memory areas where data is placed or to control the way certain functions receive parameters. The #pragma directives offer a way for each compiler to offer machine- and operating-system-specific features while retaining overall compatibility with the C and C++ languages. Pragmas are machine- or operating-system-specific by definition, and are usually different for every compiler. )
(1)message參數(shù)。Message參數(shù)是我最喜歡的一個(gè)參數(shù),它能夠在編譯信息輸出窗口中輸出相應(yīng)的信息,這對(duì)于源代碼信息的控制是非常重要的。其使用方法為:
#pragma message(“消息文本”),當(dāng)編譯器遇到這條指令時(shí)就在編譯輸出窗口中將消
息文本打印出來。
當(dāng)我們?cè)诔绦蛑卸x了許多宏來控制源代碼版本的時(shí)候,我們自己有可能都會(huì)忘記有沒有正確的設(shè)置這些宏,此時(shí)我們可以用這條指令在編譯的時(shí)候就進(jìn)行檢查。假設(shè)我們希望判斷自己有沒有在源代碼的什么地方定義了_X86這個(gè)宏可以用下面的方法:
#ifdef _X86
#pragma message(“_X86 macro activated!”)
#endif
當(dāng)我們定義了_X86這個(gè)宏以后,應(yīng)用程序在編譯時(shí)就會(huì)在編譯輸出窗口里顯示
“_X86 macro activated!”。我們就不會(huì)因?yàn)椴挥浀米约憾x的一些特定的宏而抓耳
撓腮了。
(2)另一個(gè)使用得比較多的pragma參數(shù)是code_seg。格式如:
#pragma code_seg( ["section-name"[,"section-class"] ] )
它能夠設(shè)置程序中函數(shù)代碼存放的代碼段,當(dāng)我們開發(fā)驅(qū)動(dòng)程序的時(shí)候就會(huì)使用到它。
(3)#pragma once (比較常用)。只要在頭文件的最開始加入這條指令就能夠保證
頭文件被編譯一次,這條指令實(shí)際上在VC6中就已經(jīng)有了,但是考慮到兼容性并沒有太多的使用它。
(4)#pragma hdrstop表示預(yù)編譯頭文件到此為止,后面的頭文件不進(jìn)行預(yù)編譯。BCB
可以預(yù)編譯頭文件以加快鏈接的速度,但如果所有頭文件都進(jìn)行預(yù)編譯又可能占太多磁盤空間,所以使用這個(gè)選項(xiàng)排除一些頭文件。有時(shí)單元之間有依賴關(guān)系,比如單元A依賴單元B,所以單元B要先于單元A編譯。你可以用#pragma startup指定編
譯優(yōu)先級(jí),如果使用了#pragma package(smart_init),BCB就會(huì)根據(jù)優(yōu)先級(jí)的大小先
后編譯。
(5)#pragma resource "*.dfm"表示把*.dfm文件中的資源加入工程。*.dfm中包括窗
體外觀的定義。
(6)#pragma warning(disable : 4507 34; once : 4385; error : 164 ) 等價(jià)于:
#pragma warning(disable:4507 34) // 不顯示4507和34號(hào)警告信息
#pragma warning(once:4385) // 4385號(hào)警告信息僅報(bào)告一次
#pragma warning(error:164) // 把164號(hào)警告信息作為一個(gè)錯(cuò)誤。
同時(shí)這個(gè)pragma warning 也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
這里n代表一個(gè)警告等級(jí)(1---4)。
#pragma warning( push )保存所有警告信息的現(xiàn)有的警告狀態(tài)。
#pragma warning( push, n)保存所有警告信息的現(xiàn)有的警告狀態(tài),并且全局警告
等級(jí)設(shè)定為n。
#pragma warning( pop )向棧中彈出最后一個(gè)警告信息,在入棧和出棧之間所作
的一切改動(dòng)取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
//.......
#pragma warning( pop )
在這段代碼的最后,重新保存所有的警告信息(包括4705,4706和4707)。
(7)#pragma comment(...) 該指令將一個(gè)注釋記錄放入一個(gè)對(duì)象文件或可執(zhí)行
文件中。常用的lib關(guān)鍵字,可以幫我們連入一個(gè)庫文件。
(8)#pragma pack() 我們知道在VC中,對(duì)于想結(jié)構(gòu)體Struct這樣的類型,VC采
用8字節(jié)對(duì)齊的方式,如果我們不想使用8字節(jié)對(duì)齊(在網(wǎng)絡(luò)變成中經(jīng)常需要這樣),我們可以在結(jié)構(gòu)體前面加上
#pragma pack(1)
struct
{
......
}
#pragma pack()
二.#if _MSC_VER > 1000 #pragma once #endif
(1)_MSC_VER。 Defines the compiler version. Defined as 1200 for Microsoft Visual C++ 6.0. Always defined.
(2)#if _MSC_VER > 1000的意思是指如果vc編譯器的版本大于1000則這個(gè)語句
被編譯!大概小于1000的版本不支持#pragma once這個(gè)語句。
(3)#pragma once 。Specifies that the file, in which the pragma resides,
will be included (opened) only once by the compiler in a build. A common use for this pragma is the following:
//header.h
#pragma once
// Your C or C++ code would follow:
#pragma once 加入頭文件的第一行 指示這個(gè)文件在編譯時(shí)只被編譯器文件編譯
(打開)一次!一般用到.h中防止文件被重復(fù)包括!
三.#pragma once 與 #ifndef #define #endif
(1)從定義上即可看出,pragmas指令是某種機(jī)器或者操作系統(tǒng)獨(dú)有的,并且不同編譯器也常常有別。#pragma once這個(gè)是編譯器相關(guān)指令,就是說在這個(gè)編譯系統(tǒng)
上能用,但是在其他編譯系統(tǒng) 不一定型,也就是說移植型差。不過現(xiàn)在基本上
已經(jīng)是每個(gè)編譯器都有這個(gè)定義了。
#ifndef #define #endif這個(gè)是語言支持指令,這是C/C++語言中的宏定義,通過
宏定義避免文件多次編譯。所以在所有支持C++語言的編譯器上都是有效的。如果寫的程序要 跨平臺(tái),最好使用這種方式。
(2)#ifndef #define #endif #ifndef 還有其它作用,防止頭文件重復(fù)引用只是
其中一個(gè)應(yīng)用而已。#pragma只有微軟支持。
(3)#ifndef #define #endif 他讀到#ifndef之后,如果已經(jīng)定義過了,就會(huì)跳過
這一大片,一直到#endif為止。這將增加build時(shí)間,因?yàn)槊看蝐ompiler都會(huì)打開這個(gè)文件,然后搜索全文件一遍。而如果碰到了#pragma once,他就會(huì)立刻停止,
關(guān)閉打開的這個(gè)文件。在某種程度上減少 了build時(shí)間。一般用法:
#ifndef
#define
#pragma once
.....
#endif
四. #pragma data_seg(".mdata").....#pragma data_seg()可以讓編譯器把兩者之間
的所有已初始化變量放入一個(gè)新的.mdata段中。應(yīng)用之一是單應(yīng)用程序。
有的時(shí)候我們可能想讓一個(gè)應(yīng)用程序只啟動(dòng)一次,就像單件模式(singleton)一樣,實(shí)現(xiàn)的方法可能有多種,這里說說用#pragma data_seg的實(shí)現(xiàn),很是簡(jiǎn)潔便利。
應(yīng)用程序的入口文件前面加上:
#pragma data_seg("flag_data")
int app_count = 0;
#pragma data_seg()
#pragma comment(linker,"/SECTION:flag_data,RWS")
然后程序啟動(dòng)的地方加上
if(app_count>0) // 如果計(jì)數(shù)大于0,則退出應(yīng)用程序。
{
//MessageBox(NULL, "已經(jīng)啟動(dòng)一個(gè)應(yīng)用程序", "Warning", MB_OK);
//printf("no%d application", app_count);
return FALSE;
} app_count++;
總結(jié):
1. #ifndef 由語言支持所以移植性好,#pragma 可以避免名字沖突
2. 調(diào)查一下<stdlib.h>和<iostream>等標(biāo)準(zhǔn)庫, 用得都是#ifndef, 我個(gè)人推薦這種方式.
涉及到事件分享器的兩種模式稱為:Reactor and Proactor [1]. Reactor模式是基于同步I/O的,而Proactor模式是和異步I/O相關(guān)的. 在Reactor模式中,事件分離者等待某個(gè)事件或者可應(yīng)用或個(gè)操作的狀態(tài)發(fā)生(比如文件描述符可讀寫,或者是socket可讀寫),事件分離者就把這個(gè)事件傳給事先注冊(cè)的事件處理函數(shù)或者回調(diào)函數(shù),由后者來做實(shí)際的讀寫操作。
而在Proactor模式中,事件處理者(或者代由事件分離者發(fā)起)直接發(fā)起一個(gè)異步讀寫操作(相當(dāng)于請(qǐng)求),而實(shí)際的工作是由操作系統(tǒng)來完成的。發(fā)起時(shí),需要提供的參數(shù)包括用于存放讀到數(shù)據(jù)的緩存區(qū),讀的數(shù)據(jù)大小,或者用于存放外發(fā)數(shù)據(jù)的緩存區(qū),以及這個(gè)請(qǐng)求完后的回調(diào)函數(shù)等信息。事件分離者得知了這個(gè)請(qǐng)求,它默默等待這個(gè)請(qǐng)求的完成,然后轉(zhuǎn)發(fā)完成事件給相應(yīng)的事件處理者或者回調(diào)。舉例來說,在Windows上事件處理者投遞了一個(gè)異步IO操作(稱有overlapped的技術(shù)),事件分離者等IOCompletion事件完成[1]. 這種異步模式的典型實(shí)現(xiàn)是基于操作系統(tǒng)底層異步API的,所以我們可稱之為“系統(tǒng)級(jí)別”的或者“真正意義上”的異步,因?yàn)榫唧w的讀寫是由操作系統(tǒng)代勞的。
=======================================================================
這篇文章探討并比較兩種用于TCP服務(wù)器的高性能設(shè)計(jì)模式. 除了介紹現(xiàn)有的解決方案, 還提出了一種更具伸縮性,只需要維護(hù)一份代碼并且跨平臺(tái)的解決方案(含代碼示例), 以及其在不同平臺(tái)上的微調(diào). 此文還比較了java,c#,c++對(duì)各自現(xiàn)有以及提到的解決方案的實(shí)現(xiàn)性能.
系統(tǒng)I/O 可分為阻塞型, 非阻塞同步型以及非阻塞異步型[1, 2]. 阻塞型I/O意味著控制權(quán)只到調(diào)用操作結(jié)束了才會(huì)回到調(diào)用者手里. 結(jié)果調(diào)用者被阻塞了, 這段時(shí)間了做不了任何其它事情. 更郁悶的是,在等待IO結(jié)果的時(shí)間里,調(diào)用者所在線程此時(shí)無法騰出手來去響應(yīng)其它的請(qǐng)求,這真是太浪費(fèi)資源了。拿read()操作來說吧, 調(diào)用此函數(shù)的代碼會(huì)一直僵在此處直至它所讀的socket緩存中有數(shù)據(jù)到來.
相比之下,非阻塞同步是會(huì)立即返回控制權(quán)給調(diào)用者的。調(diào)用者不需要等等,它從調(diào)用的函數(shù)獲取兩種結(jié)果:要么此次調(diào)用成功進(jìn)行了;要么系統(tǒng)返回錯(cuò)誤標(biāo)識(shí)告訴調(diào)用者當(dāng)前資源不可用,你再等等或者再試度看吧。比如read()操作, 如果當(dāng)前socket無數(shù)據(jù)可讀,則立即返回EWOULBLOCK/EAGAIN,告訴調(diào)用read()者"數(shù)據(jù)還沒準(zhǔn)備好,你稍后再試".
在非阻塞異步調(diào)用中,稍有不同。調(diào)用函數(shù)在立即返回時(shí),還告訴調(diào)用者,這次請(qǐng)求已經(jīng)開始了。系統(tǒng)會(huì)使用另外的資源或者線程來完成這次調(diào)用操作,并在完成的時(shí)候知會(huì)調(diào)用者(比如通過回調(diào)函數(shù))。拿Windows的ReadFile()或者POSIX的aio_read()來說,調(diào)用它之后,函數(shù)立即返回,操作系統(tǒng)在后臺(tái)同時(shí)開始讀操作。
在以上三種IO形式中,非阻塞異步是性能最高、伸縮性最好的。
這篇文章探討不同的I/O利用機(jī)制并提供一種跨平臺(tái)的設(shè)計(jì)模式(解決方案). 希望此文可以給于TCP高性能服務(wù)器開發(fā)者一些幫助,選擇最佳的設(shè)計(jì)方案。下面我們會(huì)比較 Java, c#, C++各自對(duì)探討方案的實(shí)現(xiàn)以及性能. 我們?cè)谖恼碌暮竺婢筒辉偬峒白枞降姆桨噶耍驗(yàn)樽枞絀/O實(shí)在是缺少可伸縮性,性能也達(dá)不到高性能服務(wù)器的要求。
兩種IO多路復(fù)用方案:Reactor and Proactor
一般情況下,I/O 復(fù)用機(jī)制需要事件分享器(event demultiplexor [1, 3]). 事件分享器的作用,即將那些讀寫事件源分發(fā)給各讀寫事件的處理者,就像送快遞的在樓下喊: 誰的什么東西送了, 快來拿吧。開發(fā)人員在開始的時(shí)候需要在分享器那里注冊(cè)感興趣的事件,并提供相應(yīng)的處理者(event handlers),或者是回調(diào)函數(shù); 事件分享器在適當(dāng)?shù)臅r(shí)候會(huì)將請(qǐng)求的事件分發(fā)給這些handler或者回調(diào)函數(shù).
涉及到事件分享器的兩種模式稱為:Reactor and Proactor [1]. Reactor模式是基于同步I/O的,而Proactor模式是和異步I/O相關(guān)的. 在Reactor模式中,事件分離者等待某個(gè)事件或者可應(yīng)用或個(gè)操作的狀態(tài)發(fā)生(比如文件描述符可讀寫,或者是socket可讀寫),事件分離者就把這個(gè)事件傳給事先注冊(cè)的事件處理函數(shù)或者回調(diào)函數(shù),由后者來做實(shí)際的讀寫操作。
而在Proactor模式中,事件處理者(或者代由事件分離者發(fā)起)直接發(fā)起一個(gè)異步讀寫操作(相當(dāng)于請(qǐng)求),而實(shí)際的工作是由操作系統(tǒng)來完成的。發(fā)起時(shí),需要提供的參數(shù)包括用于存放讀到數(shù)據(jù)的緩存區(qū),讀的數(shù)據(jù)大小,或者用于存放外發(fā)數(shù)據(jù)的緩存區(qū),以及這個(gè)請(qǐng)求完后的回調(diào)函數(shù)等信息。事件分離者得知了這個(gè)請(qǐng)求,它默默等待這個(gè)請(qǐng)求的完成,然后轉(zhuǎn)發(fā)完成事件給相應(yīng)的事件處理者或者回調(diào)。舉例來說,在Windows上事件處理者投遞了一個(gè)異步IO操作(稱有overlapped的技術(shù)),事件分離者等IOCompletion事件完成[1]. 這種異步模式的典型實(shí)現(xiàn)是基于操作系統(tǒng)底層異步API的,所以我們可稱之為“系統(tǒng)級(jí)別”的或者“真正意義上”的異步,因?yàn)榫唧w的讀寫是由操作系統(tǒng)代勞的。
舉另外個(gè)例子來更好地理解Reactor與Proactor兩種模式的區(qū)別。這里我們只關(guān)注read操作,因?yàn)閣rite操作也是差不多的。下面是Reactor的做法:
- 某個(gè)事件處理者宣稱它對(duì)某個(gè)socket上的讀事件很感興趣;
- 事件分離者等著這個(gè)事件的發(fā)生;
- 當(dāng)事件發(fā)生了,事件分離器被喚醒,這負(fù)責(zé)通知先前那個(gè)事件處理者;
- 事件處理者收到消息,于是去那個(gè)socket上讀數(shù)據(jù)了. 如果需要,它再次宣稱對(duì)這個(gè)socket上的讀事件感興趣,一直重復(fù)上面的步驟;
下面再來看看真正意義的異步模式Proactor是如何做的:
- 事件處理者直接投遞發(fā)一個(gè)寫操作(當(dāng)然,操作系統(tǒng)必須支持這個(gè)異步操作). 這個(gè)時(shí)候,事件處理者根本不關(guān)心讀事件,它只管發(fā)這么個(gè)請(qǐng)求,它魂?duì)繅?mèng)縈的是這個(gè)寫操作的完成事件。這個(gè)處理者很拽,發(fā)個(gè)命令就不管具體的事情了,只等著別人(系統(tǒng))幫他搞定的時(shí)候給他回個(gè)話。
- 事件分離者等著這個(gè)讀事件的完成(比較下與Reactor的不同);
- 當(dāng)事件分離者默默等待完成事情到來的同時(shí),操作系統(tǒng)已經(jīng)在一邊開始干活了,它從目標(biāo)讀取數(shù)據(jù),放入用戶提供的緩存區(qū)中,最后通知事件分離者,這個(gè)事情我搞完了;
- 事件分享者通知之前的事件處理者: 你吩咐的事情搞定了;
- 事件處理者這時(shí)會(huì)發(fā)現(xiàn)想要讀的數(shù)據(jù)已經(jīng)乖乖地放在他提供的緩存區(qū)中,想怎么處理都行了。如果有需要,事件處理者還像之前一樣發(fā)起另外一個(gè)寫操作,和上面的幾個(gè)步驟一樣。
現(xiàn)行做法
開源C++開發(fā)框架 ACE[1, 3](Douglas Schmidt, et al.開發(fā)) 提供了大量平臺(tái)獨(dú)立的底層并發(fā)支持類(線程、互斥量等). 同時(shí)在更高一層它也提供了獨(dú)立的幾組C++類,用于實(shí)現(xiàn)Reactor及Proactor模式。 盡管它們都是平臺(tái)獨(dú)立的單元,但他們都提供了不同的接口.
ACE Proactor在MS-Windows上無論是性能還在健壯性都更勝一籌,這主要是由于Windows提供了一系列高效的底層異步API. [4, 5].
(這段可能過時(shí)了點(diǎn)吧) 不幸的是,并不是所有操作系統(tǒng)都為底層異步提供健壯的支持。舉例來說, 許多Unix系統(tǒng)就有麻煩.因此, ACE Reactor可能是Unix系統(tǒng)上更合適的解決方案. 正因?yàn)橄到y(tǒng)底層的支持力度不一,為了在各系統(tǒng)上有更好的性能,開發(fā)者不得不維護(hù)獨(dú)立的好幾份代碼: 為Windows準(zhǔn)備的ACE Proactor以及為Unix系列提供的ACE Reactor.
就像我們提到過的,真正的異步模式需要操作系統(tǒng)級(jí)別的支持。由于事件處理者及操作系統(tǒng)交互的差異,為Reactor和Proactor設(shè)計(jì)一種通用統(tǒng)一的外部接口是非常困難的。這也是設(shè)計(jì)通行開發(fā)框架的難點(diǎn)所在。
更好的解決方案
在文章這一段時(shí),我們將嘗試提供一種融合了Proactor和Reactor兩種模式的解決方案. 為了演示這個(gè)方案,我們將Reactor稍做調(diào)整,模擬成異步的Proactor模型(主要是在事件分離器里完成本該事件處理者做的實(shí)際讀寫工作,我們稱這種方法為"模擬異步")。 下面的示例可以看看read操作是如何完成的:
- 事件處理者宣稱對(duì)讀事件感興趣,并提供了用于存儲(chǔ)結(jié)果的緩存區(qū)、讀數(shù)據(jù)長(zhǎng)度等參數(shù);
- 調(diào)試者等待(比如通過select());
- 當(dāng)有事件到來(即可讀),調(diào)試者被喚醒, 調(diào)試者去執(zhí)行非阻塞的讀操作(前面事件處理者已經(jīng)給了足夠的信息了)。讀完后,它去通知事件處理者。
- 事件處理者這時(shí)被知會(huì)讀操作已完成,它擁有完整的原先想要獲取的數(shù)據(jù)了.
我們看到,通過為分離者(也就上面的調(diào)試者)添加一些功能,可以讓Reactor模式轉(zhuǎn)換為Proactor模式。所有這些被執(zhí)行的操作,其實(shí)是和Reactor模型應(yīng)用時(shí)完全一致的。我們只是把工作打散分配給不同的角色去完成而已。這樣并不會(huì)有額外的開銷,也不會(huì)有性能上的的損失,我們可以再仔細(xì)看看下面的兩個(gè)過程,他們實(shí)際上完成了一樣的事情:
標(biāo)準(zhǔn)的經(jīng)典的 Reactor模式:
- 步驟 1) 等待事件 (Reactor 的工作)
- 步驟 2) 發(fā)"已經(jīng)可讀"事件發(fā)給事先注冊(cè)的事件處理者或者回調(diào) ( Reactor 要做的)
- 步驟 3) 讀數(shù)據(jù) (用戶代碼要做的)
- 步驟 4) 處理數(shù)據(jù) (用戶代碼要做的)
模擬的Proactor模式:
- 步驟 1) 等待事件 (Proactor 的工作)
- 步驟 2) 讀數(shù)據(jù)(看,這里變成成了讓 Proactor 做這個(gè)事情)
- 步驟 3) 把數(shù)據(jù)已經(jīng)準(zhǔn)備好的消息給用戶處理函數(shù),即事件處理者(Proactor 要做的)
- 步驟 4) 處理數(shù)據(jù) (用戶代碼要做的)
在沒有底層異步I/O API支持的操作系統(tǒng),這種方法可以幫我們隱藏掉socket接口的差異(無論是性能還是其它), 提供一個(gè)完全可用的統(tǒng)一"異步接口"。這樣我們就可以開發(fā)真正平臺(tái)獨(dú)立的通用接口了。
TProactor
我們提出的TProactor方案已經(jīng)由TerabitP/L [6]公司實(shí)現(xiàn)了. 它有兩種實(shí)現(xiàn): C++的和Java的.C++版本使用了ACE平臺(tái)獨(dú)立的底層元件,最終在所有操作系統(tǒng)上提供了統(tǒng)一的異步接口。
TProactor中最重要的組件要數(shù)Engine和WaitStrategy了. Engine用于維護(hù)異步操作的生命周期;而WaitStrategy用于管理并發(fā)策略. WaitStrategy和Engine一般是成對(duì)出現(xiàn)的, 兩者間提供了良好的匹配接口.
Engines和等待策略被設(shè)計(jì)成高度可組合的(完整的實(shí)現(xiàn)列表請(qǐng)參照附錄1)。TProactor是高度可配置的方案,通過使用異步內(nèi)核API和同步Unix API(select(), poll(), /dev/poll (Solaris 5.8+), port_get (Solaris 5.10),RealTime (RT) signals (Linux 2.4+), epoll (Linux 2.6), k-queue (FreeBSD) ),它內(nèi)部實(shí)現(xiàn)了三種引擎(POSIX AIO, SUN AIO and Emulated AIO)并隱藏了六類等待策略。TProactor實(shí)現(xiàn)了和標(biāo)準(zhǔn)的 ACE Proactor一樣的接口。這樣一來,為不同平臺(tái)提供通用統(tǒng)一的只有一份代碼的跨平臺(tái)解決方案成為可能。
Engines和WaitStrategies可以像樂高積木一樣自由地組合,開發(fā)者可以在運(yùn)行時(shí)通過配置參數(shù)來選擇合適的內(nèi)部機(jī)制(引擎和等待策略)。可以根據(jù)需求設(shè)定配置,比如連接數(shù),系統(tǒng)伸縮性,以及運(yùn)行的操作系統(tǒng)等。如果系統(tǒng)支持相應(yīng)的異步底層API,開發(fā)人員可以選擇真正的異步策略,否則用戶也可以選擇使用模擬出來的異步模式。所有這一切策略上的實(shí)現(xiàn)細(xì)節(jié)都不太需要關(guān)注,我們看到的是一個(gè)可用的異步模型。
舉例來說,對(duì)于運(yùn)行在Sun Solaris上的HTTP服務(wù)器,如果需要支持大量的連接數(shù),/dev/poll或者port_get()之類的引擎是比較合適的選擇;如果需要高吞吐量,那使用基本select()的引擎會(huì)更好。由于不同選擇策略內(nèi)在算法的問題,像這樣的彈性選擇是標(biāo)準(zhǔn)ACE Reactor/Proactor模式所無法提供的(見附錄2)。
在性能方面,我們的測(cè)試顯示,模擬異步模式并未造成任何開銷,沒有變慢,反倒是性能有所提升。根據(jù)我們的測(cè)試結(jié)果,TProactor相較標(biāo)簽的ACE Reactor在Unix/Linux系統(tǒng)上有大約10-35%性能提升,而在Windows上差不多(測(cè)試了吞吐量及響應(yīng)時(shí)間)。
性能比較 (JAVA / C++ / C#).
除了C++,我們也在Java中實(shí)現(xiàn)了TProactor. JDK1.4中, Java僅提供了同步方法, 像C中的select() [7, 8]. Java TProactor基于Java的非阻塞功能(java.nio包),類似于C++的TProactor使用了select()引擎.
圖1、2顯示了以 bits/sec為單位的傳輸速度以及相應(yīng)的連接數(shù)。這些圖比較了以下三種方式實(shí)現(xiàn)的echo服務(wù)器:標(biāo)準(zhǔn)ACE Reactor實(shí)現(xiàn)(基于RedHat Linux9.0)、TProactor C++/Java實(shí)現(xiàn)(Microsoft Windows平臺(tái)及RedHat v9.0), 以及C#實(shí)現(xiàn)。測(cè)試的時(shí)候,三種服務(wù)器使用相同的客戶端瘋狂地連接,不間斷地發(fā)送固定大小的數(shù)據(jù)包。
這幾組測(cè)試是在相同的硬件上做的,在不同硬件上做的相對(duì)結(jié)果對(duì)比也是類似。
圖 1. Windows XP/P4 2.6GHz HyperThreading/512 MB RAM.
圖 2. Linux RedHat 2.4.20-smp/P4 2.6GHz HyperThreading/512 MB RAM.
用戶代碼示例
下面是TProactor Java實(shí)現(xiàn)的echo服務(wù)器代碼框架。總的來說,開發(fā)者只需要實(shí)現(xiàn)兩個(gè)接口:一是OpRead,提供存放讀結(jié)果的緩存;二是OpWrite,提供存儲(chǔ)待寫數(shù)據(jù)的緩存區(qū)。同時(shí),開發(fā)者需要通過回調(diào)onReadComplated()和onWriteCompleted()實(shí)現(xiàn)協(xié)議相關(guān)的業(yè)務(wù)代碼。這些回調(diào)會(huì)在合適的時(shí)候被調(diào)用.
class EchoServerProtocol implements AsynchHandler
{
AsynchChannel achannel = null;
EchoServerProtocol( Demultiplexor m, SelectableChannel channel )
throws Exception
{
this.achannel = new AsynchChannel( m, this, channel );
}
public void start() throws Exception
{
// called after construction
System.out.println( Thread.currentThread().getName() +
": EchoServer protocol started" );
achannel.read( buffer);
}
public void onReadCompleted( OpRead opRead ) throws Exception
{
if ( opRead.getError() != null )
{
// handle error, do clean-up if needed
System.out.println( "EchoServer::readCompleted: " +
opRead.getError().toString());
achannel.close();
return;
}
if ( opRead.getBytesCompleted () <= 0)
{
System.out.println("EchoServer::readCompleted: Peer closed "
+ opRead.getBytesCompleted();
achannel.close();
return;
}
ByteBuffer buffer = opRead.getBuffer();
achannel.write(buffer);
}
public void onWriteCompleted(OpWrite opWrite)
throws Exception
{
// logically similar to onReadCompleted
...
}
}
結(jié)束語
TProactor為多個(gè)平臺(tái)提供了一個(gè)通用、彈性、可配置的高性能通訊組件,所有那些在附錄2中提到的問題都被很好地隱藏在內(nèi)部實(shí)現(xiàn)中了。
從上面的圖中我們可以看出C++仍舊是編寫高性能服務(wù)器最佳選擇,雖然Java已緊隨其后。然而因?yàn)镴ava本身實(shí)現(xiàn)上的問題,其在Windows上表現(xiàn)不佳(這已經(jīng)應(yīng)該成為歷史了吧)。
需要注意的是,以上針對(duì)Java的測(cè)試,都是以裸數(shù)據(jù)的形式測(cè)試的,未涉及到數(shù)據(jù)的處理(影響性能)。
縱觀AIO在Linux上的快速發(fā)展[9], 我們可以預(yù)計(jì)Linux內(nèi)核API將會(huì)提供大量更加強(qiáng)健的異步API, 如此一來以后基于此而實(shí)現(xiàn)的新的Engine/等待策略將能輕松地解決能用性方面的問題,并且這也能讓標(biāo)準(zhǔn)ACE Proactor接口受益。
附錄 I
TProactor中實(shí)現(xiàn)的Engines 和 等待策略
引擎類型 |
等待策略 |
操作系統(tǒng) |
POSIX_AIO (true async)
aio_read() /aio_write() |
aio_suspend() Waiting for RT signal Callback function |
POSIX complained UNIX (not robust) POSIX (not robust) SGI IRIX, LINUX (not robust) |
SUN_AIO (true async)
aio_read() /aio_write() |
aio_wait() |
SUN (not robust) |
Emulated Async Non-blocking read() /write() |
select()
poll() /dev/poll Linux RT signals Kqueue |
generic POSIX Mostly all POSIX implementations SUN Linux FreeBSD
|
附錄 II
所有同步等待策略可劃分為兩組:
- edge-triggered (e.g. Linux實(shí)時(shí)信號(hào)) - signal readiness only when socket became ready (changes state);
- level-triggered (e.g.
select()
, poll()
, /dev/poll) - readiness at any time.
讓我們看看這兩組的一些普遍的邏輯問題:
- edge-triggered group: after executing I/O operation, the demultiplexing loop can lose the state of socket readiness. Example: the "read" handler did not read whole chunk of data, so the socket remains still ready for read. But the demultiplexor loop will not receive next notification.
- level-triggered group: when demultiplexor loop detects readiness, it starts the write/read user defined handler. But before the start, it should remove socket descriptior from theset of monitored descriptors. Otherwise, the same event can be dispatched twice.
- Obviously, solving these problems adds extra complexities to development. All these problems were resolved internally within TProactor and the developer should not worry about those details, while in the synch approach one needs to apply extra effort to resolve them.
在網(wǎng)絡(luò)應(yīng)用服務(wù)器端, 為了性能和防止阻塞, 經(jīng)常會(huì)把邏輯處理和I/O處理分離:
I/O網(wǎng)絡(luò)線程處理I/O事件: 數(shù)據(jù)包的接收和發(fā)送, 連接的建立和維護(hù)等.
邏輯線程要對(duì)收到的數(shù)據(jù)包進(jìn)行邏輯處理.
通常網(wǎng)絡(luò)線程和邏輯線程之間是通過數(shù)據(jù)包隊(duì)列來交換信息, 簡(jiǎn)單來說就是一個(gè)生產(chǎn)者-消費(fèi)者模式.
這個(gè)隊(duì)列是多個(gè)線程在共享訪問必須加鎖, 意味著每次訪問都要加鎖。如何更好的如何減少鎖競(jìng)爭(zhēng)次數(shù)呢 ?
方案一 雙緩沖消息隊(duì)列:
兩個(gè)隊(duì)列,一個(gè)給邏輯線程讀,一個(gè)給IO線程用來寫,當(dāng)邏輯線程讀完隊(duì)列后會(huì)將自己的隊(duì)列與IO線程的隊(duì)列相調(diào)換。
IO線程每次寫隊(duì)列時(shí)都要加鎖,邏輯線程在調(diào)換隊(duì)列時(shí)也需要加鎖,但邏輯線程在讀隊(duì)列時(shí)是不需要加鎖的.
隊(duì)列緩沖區(qū)的大小要根據(jù)數(shù)據(jù)量的大小進(jìn)行調(diào)整的,如果緩沖區(qū)很小,就能更及時(shí)的處理數(shù)據(jù),但吞吐量以及出現(xiàn)資源競(jìng)爭(zhēng)的幾率大多了。
可以給緩沖隊(duì)列設(shè)置最大上限,超過上限的數(shù)量之后,將包丟棄不插入隊(duì)列。
另外,雙緩沖的實(shí)現(xiàn)也有不同策略的,
一是讀操作優(yōu)先,就是生產(chǎn)者只要發(fā)現(xiàn)空閑緩沖,馬上swap,
二是寫線程只有在當(dāng)前的緩沖區(qū)寫滿了,才進(jìn)行swap操作。
三是上層邏輯按照幀率來處理,每一幀的時(shí)候?qū)㈦p層緩沖隊(duì)列調(diào)換一下,取一個(gè)隊(duì)列來處理即可
方案二 提供一個(gè)隊(duì)列容器:
提供一個(gè)隊(duì)列容器,里面有多個(gè)隊(duì)列,每個(gè)隊(duì)列都可固定存放一定數(shù)量的消息。網(wǎng)絡(luò)IO線程要給邏輯線程投遞消息時(shí),會(huì)從隊(duì)列容器中取一個(gè)空隊(duì)列來使用,直到將該隊(duì)列填滿后再放回容器中換另一個(gè)空隊(duì)列。而邏輯線程取消息時(shí)是從隊(duì)列容器中取一個(gè)有消息的隊(duì)列來讀取,處理完后清空隊(duì)列再放回到容器中。
這樣便使得只有在對(duì)隊(duì)列容器進(jìn)行操作時(shí)才需要加鎖,而IO線程和邏輯線程在操作自己當(dāng)前使用的隊(duì)列時(shí)都不需要加鎖,所以鎖競(jìng)爭(zhēng)的機(jī)會(huì)大大減少了。
這里為每個(gè)隊(duì)列設(shè)了個(gè)最大消息數(shù),看來好像是打算只有當(dāng)IO線程寫滿隊(duì)列時(shí)才會(huì)將其放回到容器中換另一個(gè)隊(duì)列。那這樣有時(shí)也會(huì)出現(xiàn)IO線程未寫滿一個(gè)隊(duì)列,而邏輯線程又沒有數(shù)據(jù)可處理的情況,特別是當(dāng)數(shù)據(jù)量很少時(shí)可能會(huì)很容易出現(xiàn)[這個(gè)可以通過設(shè)置超時(shí)來處理, 如果當(dāng)前時(shí)間-向隊(duì)列放入第一個(gè)包的時(shí)間 > 50 ms, 就將其放回到容器中換另一個(gè)隊(duì)列]。
通常我們邏輯服務(wù)器會(huì)以場(chǎng)景來劃分線程,不同線程執(zhí)行不同場(chǎng)景.一個(gè)線程可以執(zhí)行多個(gè)場(chǎng)景.因?yàn)橥婕覍儆趫?chǎng)景,我們會(huì)把玩家數(shù)據(jù),包括其緩沖池丟給場(chǎng)景 去處理.
怎么下載MANGOS源碼
mangos-gui Mangos的GUI版控制工具(svn地址) http://mangos-gui.googlecode.com/svn/trunk/
MangosWFE網(wǎng)站系統(tǒng)(SVN更新地址) http://opensvn.csie.org/MangosWFE/
mangos-luascript腳本(svn更新地址) http://mangos-luascript.googlecode.com/svn/trunk
HTTP地址 http://download.toaoto.cn FTP地址 ftp://ftp.toaoto.cn FTP帳號(hào)和密碼:wow Mangos SVN: http://opensvn.csie.org/mangoswebsite MangosWeb
http://svn.sourceforge.net/viewvc/mangos/trunk/?view=log MangosLog
http://svn.sourceforge.net/svnroot/mangos/trunk/ MangosTrunk
http://www.mangosproject.org/forum/ MangosForum
鑒于有些新手不會(huì)下載源碼...所以我寫個(gè)帖子出來..給新手參考參考. 1:先下載TortoiseSVN-1.3.5.6804-svn-1.3.2這個(gè)工具 LanguagePack-1.3.5.6804-win32-zh_CN(這個(gè)是語言補(bǔ)丁) 下下來安裝好以后在Setting里面改下語言就OK了 http://luwakin.gbdisk.com/這位帥哥的網(wǎng)絡(luò)硬盤里面有! 然后安裝...........(廢話....)
2:源碼地址 https://opensvn.csie.org/ScriptDev/trunk ---------------ScriptDev
https://mangos.svn.sourceforge.net/svnroot/mangos--------------Mangos
Silver DataBase Communities SVN is located here: https://opensvn.csie.org/SDB/ https://opensvn.csie.org/traccgi/SDB/trac.cgi/timeline
SVN contains the latest table data and DB updates, in XML or SQL formats only. Full DB releases can be found in "Index of the Released DBs" section.
https://opensvn.csie.org/SDB
https://opensvn.csie.org/mangoDB
http://opensvn.csie.org/MangosDatabaseProject
3:參照我的圖下載吧!!!! (1)先創(chuàng)建個(gè)文件夾.然后右鍵選SVN取出,在里面輸入上面的源碼地址! (2)然后確定后就自動(dòng)下載了.下載完畢要導(dǎo)出下載的文件...參照最后張圖導(dǎo)出就OK啦!
1) 首先,你需要有2個(gè)軟件,第一個(gè)就是下載及更新MANGOS源碼用的TortoiseSVN.第二個(gè)就是VS2003或者VS2005. 注意:這里推薦使用VS2003.因?yàn)樵谑褂肰S2005時(shí),出現(xiàn)了很多錯(cuò)誤...導(dǎo)致編譯失敗... 而用VS2003時(shí),沒有出現(xiàn)任何問題.
MaNGOS
2) 在作完第一步后,我們可以在電腦里新建一個(gè)文件夾.這里以C:\盤來舉例.例如C:\mangos. 在建好的文件夾上點(diǎn)擊右鍵.你會(huì)看到"SVN Checkout..." , 點(diǎn)了...跳出來一個(gè)框框, 在"URL of repository"里面輸入"https://mangos.svn.sourceforge.net/svnroot/mangos" 在這個(gè)下面有一個(gè)選項(xiàng),你需要選中"HEAD",好了...點(diǎn)擊OK吧.
3) 作完上面這一步后, 用右鍵點(diǎn)擊MANGOS文件夾,你會(huì)看到"SVN Update",點(diǎn)擊他吧,這會(huì)讓你的MANGOS源碼升級(jí),以后每次只要點(diǎn)擊"SVN Update",就可以讓你的MANGOS源碼保持在最新版.
4) 接下來,就是開始編譯MANGOS了~~~是不是很興奮了... 打開VS2003, 選擇"Open Project",然后打開"C:\Mangos\win\" 選擇"mangosdVC71.sln" 如果你用的是VS2005, 就要打開"mangosdVC80.sln"
5) 接下來的這一步,我建議你看一下圖片來明白你需要作什么.
6) 呵呵.這一步就是等待了.編譯是需要時(shí)間的...等VS幫你作好吧.
7)嗯...過了一會(huì)~~~ 編譯好了.
你可以"C:\Mangos\bin\release\"里面找到編譯好的MANGOS. 需要的是這幾個(gè)文件
libeay32.dll, libmySQL.dll, mangosd.exe, MaNGOSScript.dll, realmd.exe
嗯, 好像還少什么
mangosd.conf和realmd.conf是不是, 我們可以在"C:\Mangos\src\mangosd\"和C:\mangos\src\realmd\"里面找到mangosd.conf.in , realmd.conf.in 啊 名字不一樣 嗯 把后面的.in去掉吧.
SQL文件了可以在"C:\Mangos\sql\"里面找到...當(dāng)然只有一個(gè)架構(gòu)的..嘿嘿... 升級(jí)的SQL文件可以在"E:\Mangos\sql\updates"里面找到...
ScriptDev
下載最新版本的ScriptDev并將和MaNGOS一起使用,你需要以下幾個(gè)步驟: 1) 新建一個(gè)文件夾,并命名。在文件夾上點(diǎn)右鍵。。。 如同上面Mangos源碼的下載方式一樣, 使用SVN Checkout, URL地址為 https://opensvn.csie.org/ScriptDev/trunk , 然后使用 SVN Update 2) 在作完上面的步驟后,在這個(gè)文件夾上點(diǎn)右鍵, 選擇“TortoiseSVN” ,再選擇 “Export“ 選擇一個(gè)新建文件夾.然后點(diǎn)OK,好了 最新版本的ScriptDev已經(jīng)出來了 3) 到Export好的這個(gè)新文件夾里面,復(fù)制 SQL, SRC, WIN 這三個(gè)文件夾到Mangos文件夾里, 好了,當(dāng)你編譯MANGOS時(shí),將帶著最新版本的ScriptDev... 重要:當(dāng)有ScriptDev的新版出來時(shí)。。。你必需重復(fù)上面的動(dòng)作。。。
好了...一個(gè)最新版本的MANGOS在你手上出現(xiàn)了...是由你自己完成的哦...是不是特別興奮~~~
下面的需要的軟件,推薦使用2003...2005好多問題的說... TortoiseSVN 點(diǎn)此下載 VC++ Express Edition 點(diǎn)此下載
數(shù)據(jù)庫可以用990100的0712數(shù)據(jù)庫...
http://bbs.99nets.com/read.php?tid=426801&fpage=2
編譯好的MANGOS如何安裝也可以參考990100的說明...
好了。。。帶著ScriptDev的MaNGOS將成為真正的MaNGOS最新版。。。 |
相信做開發(fā)的沒有不重視效率的。開發(fā)C#,VB的都知道,我們很依賴VS,或者說,我們很感謝VS。能夠?qū)σ粋€(gè)IDE產(chǎn)生依賴,說明這個(gè)IDE確實(shí) 有它的獨(dú)特之處。無容置疑,VS是一個(gè)非常強(qiáng)大的IDE,它支持多語言編輯。支持C#,VB,C/C++,HTML......它擁有強(qiáng)大的調(diào)試編譯功 能。它讓我們不用去記住那些安裝,環(huán)境變量設(shè)置,服務(wù)器設(shè)置,編譯的繁瑣過程。高度集成化。凡事有利有弊,在敏捷開發(fā)盛行的時(shí)代,VS是否值得我們使用是 無容置疑的。但是強(qiáng)大的VS也擁有眾多的設(shè)置,眾多的技巧。記住某些小技巧可以讓我們更加方便,快捷地使用VS。這是很有必要的。每個(gè)人或多或少記住了一 些小技巧。但是不可能全部都記住,我們按照我們自己的編程習(xí)慣記住一些自己比較常用的就好。
下面是鄙人在編碼過程中發(fā)現(xiàn)而且比較經(jīng)常使用的一些小技巧,希望對(duì)你有所幫助。
1.行編輯(復(fù)制,剪切,刪除,交換)
當(dāng)你在光標(biāo)停留行使用快捷鍵Ctrl+C,X,L時(shí),可以復(fù)制,剪切,刪除整行內(nèi)容。當(dāng)然,右鍵也是可以的。跟平時(shí)的復(fù)制,剪切,刪除就是選中和沒選中代碼的區(qū)別而已。
如果你想交換上下兩行,你可以使用快捷鍵(Shift+Alt+T),前提是光標(biāo)要停留在上面那一行。替換之后,光標(biāo)會(huì)一直跟隨原本的那一行。
2.注釋(//TODO:...)
看標(biāo)題的話,你可能想打我。那個(gè)程序員不知道注釋啊,不就//或者/*.....*/亦或者(HTML/XML注釋)。但是使用過
的,估計(jì)是少數(shù)吧。如果你喜歡用“任務(wù)列表”記錄一些要做的事情,這個(gè)小功能最適合你了。你可以再VS 2010的菜單上找到任務(wù)列表窗,點(diǎn)擊“菜單->視圖->任務(wù)列表”,你也可以點(diǎn)擊快捷鍵“Ctrl+W,T”。VS還提供了,HACK,UNTODU,UnresolvedMergeConflict標(biāo)記注釋,你可以在“工具->選項(xiàng)->環(huán)境->任務(wù)列表”找到并且編輯/添加/刪除標(biāo)記注釋。下面是圖示:


3.創(chuàng)建區(qū)域(#region和#endregion)
當(dāng)代碼越來越多的時(shí)候,你會(huì)期望可以隱藏一些代碼,而#region 和#endregion 就是這樣的功能。你可以在任何位置隱藏任何代碼。即使是隱藏的內(nèi)容不屬于同一個(gè)函數(shù)。你可以點(diǎn)擊#region旁邊的+/-,展開/隱藏代碼。在隱藏的時(shí) 候,當(dāng)你的光標(biāo)放放置在備注上面的時(shí)候,VS會(huì)顯示出隱藏的代碼內(nèi)容。

4.選擇一個(gè)單詞/選擇一個(gè)字符串
如你所知雙擊一個(gè)單詞的時(shí)候會(huì)選擇整個(gè)單詞。按住Ctrl鍵單擊單詞的任意位置同樣可以選中單詞。
雙擊字符串第一個(gè)引號(hào)的左側(cè)可以選中整個(gè)字符串。按住Ctrl鍵單擊第一個(gè)引號(hào)的前面同樣可以選中整個(gè)字符串。
5.將代碼放入工具箱
工具箱是拿來放控件的地方。我們?cè)谑褂每丶臅r(shí)候,只需要從控件當(dāng)中拖動(dòng)控件到代碼就可以了,這樣可以省去大量代碼的編輯工作。既然工具箱如此方便,那么是否可以將一段重用性很高的代碼放入工具箱呢。答案當(dāng)然是可以的。
你可以選中你的代碼,然后拖入工具箱的空白處,你的代碼就保存到工具箱了。就像你將控件拖 入代碼頁面一樣,也可以將代碼拖入工具箱中。以后你就可以像使用控件一樣使用重用的代碼。這是非常方便的。而且工具箱的內(nèi)容不會(huì)因?yàn)槟汴P(guān)閉VS而消失,在 你下次打開VS的時(shí)候工具箱同樣保存了你的代碼。如果你需要查看工具箱保存的代碼而又不想拖到代碼頁面中,你只需要將光標(biāo)停留在工具箱的代碼圖標(biāo)上面。如 圖所示:

6.格式化代碼
這個(gè)很重要,即使VS在你每次打完“;”回車之后會(huì)自動(dòng)格式化代碼。但是難免代碼的格式會(huì)有所變化,譬如粘貼一段代碼之后,代碼的格式往往會(huì)受到影響。所以,這個(gè)還是很有必要知道的。
格式化部分代碼:選中代碼->Ctrl+K,F。或者Ctrl+E,F。
格式化整個(gè)文檔:編輯->高級(jí)->設(shè)置文檔的格式。或者 Ctrl+K,D。或者Ctrl+E,D。
7.切換設(shè)計(jì)/代碼圖示
在ASP.NET頁面切換(HTML): Ctrl+PgUp/Ctrl+PgDn
在windows窗體切換:F7/Shift+F7 (代碼/設(shè)計(jì))
8.查找錯(cuò)誤代碼。
當(dāng)錯(cuò)誤列表有錯(cuò)誤或者警告提示時(shí),你可以雙擊這個(gè)錯(cuò)誤或提示,就可以跳轉(zhuǎn)到錯(cuò)誤或警告的語句前。
9.跳轉(zhuǎn)到指定行號(hào)
如果代碼很多的時(shí)候,這是很有用的。在ASP.NET編程的時(shí)候,很多錯(cuò)誤只有在運(yùn)行網(wǎng)站的時(shí)候才能發(fā)現(xiàn),而這個(gè)錯(cuò)誤又沒被在錯(cuò)誤列表提示的時(shí)候,你就可以使用這個(gè)小技巧跳到錯(cuò)誤的代碼前面。
雙擊右下角狀態(tài)欄的行號(hào),會(huì)跳出一個(gè)行號(hào)跳轉(zhuǎn)窗體。或者快捷鍵Ctrl+G調(diào)出窗體。當(dāng)然,還可以從菜單欄點(diǎn)擊“編輯->跳轉(zhuǎn)..”使用這個(gè)功能。

10.快速查找
光標(biāo)停留在需要查找的詞上面,使用快捷鍵Ctrl+F3可以跳轉(zhuǎn)到下一個(gè)相同的詞。按Shift+F3可以往上查找。
11.查找“{/}”
查找:你是否很煩惱有些對(duì)應(yīng)的標(biāo)記找的到頭找不到尾,找得到尾不知道那個(gè)是頭。當(dāng)你把光標(biāo)放在“{”的前面,VS會(huì)將相對(duì)應(yīng)的”}“標(biāo)記起來。你也可以將光標(biāo)停留在“}”的后面,可以達(dá)到同樣的效果。

12.查找和替換
當(dāng)你想查找/替換掉某個(gè)字符串的時(shí)候,你可以按快捷鍵“Ctrl+F”或者“Ctrl+H”,進(jìn)行這一操作。另外VS支持正則表達(dá)式和通配符。
如果你想從整個(gè)項(xiàng)目進(jìn)行查找/替換,你需要使用快捷鍵“Ctrl+Shift+F”或者“Ctrl+Shift+H”。當(dāng)然這一切都可以在菜單欄找到。“編輯->查找和替換”。當(dāng)你想中止全局替換的時(shí)候,你可以使用快捷鍵“Ctrl+Pause Break”。

13. 書簽
書簽是很有用的功能,用過Chrome的都知道。在VS當(dāng)中,書簽同樣適用。它可以幫你保存位置,以便你寫代碼。
放置書簽:Ctrl+B,T。
上一個(gè)書簽:Ctrl+B,P。
下一個(gè)書簽:Ctrl+B,N。
刪除所有書簽:Ctrl+K,C。
除此之外,VS還提供了其它的書簽操作。

14.跳轉(zhuǎn)到定義
當(dāng)你查看代碼的時(shí)候,往往需要去查看原函數(shù),這是難免的。但是千萬不要去手動(dòng)尋找函數(shù)。這效率往往是很低的。你可以右鍵該函數(shù),選擇跳轉(zhuǎn)到定義即可。當(dāng)然你也可以使用快捷鍵F12。

15.以文本形式插入外部文本
“菜單->編輯->將文件作為文本插入”
好處是,你不需要打開文件去復(fù)制粘貼。

或許這些小技巧你早就知道了。亦或是,你覺得這些技巧根本沒啥用。當(dāng)然,我們最主要的任務(wù)還是去編碼而已。沒有必要將心思花在這上面。但是,當(dāng)你習(xí)慣使用這些小技巧的時(shí)候,這為你帶來的收益覺得不會(huì)讓你有所失望的。有些技巧,個(gè)人認(rèn)為還是很有必要掌握的。
總之,撿你想撿的吧,讓其他人折騰去吧。
@飯中淹
多謝樓主解答,不過還是有一些疑問,我說一下自己對(duì)這個(gè)架構(gòu)的理解。
從用戶登錄開始,用戶登錄連接網(wǎng)關(guān),發(fā)數(shù)據(jù)到loginserver校驗(yàn)賬戶密碼,如果areaDB中沒有賬戶信息,向數(shù)據(jù)中心要賬戶密碼,插入areaDB,以后校驗(yàn)賬戶就可以直接在區(qū)域DB中做了,如果賬戶密碼校驗(yàn)成功,發(fā)送本區(qū)的組列表給客戶端,玩家選擇某個(gè)服務(wù)器,login獲取生成一個(gè)key發(fā)給userserver,同時(shí)把key發(fā)給客戶端以及一個(gè)網(wǎng)關(guān)的ip端口,客戶端使用其連接,發(fā)送key到Userserver,比對(duì)key,非法踢掉客戶端連接,合法userserver向數(shù)據(jù)中心獲取賬戶詳細(xì)信息,比如賬戶上剩余點(diǎn)卡等,同時(shí)向GroupDB獲取本組中的自己的角色,得到這些信息都發(fā)給客戶端,客戶端選擇一個(gè)角色進(jìn)入游戲,userserver從數(shù)據(jù)庫讀取該角色的完全信息,根據(jù)玩家之前的位置確定進(jìn)入那個(gè)gameserver(我認(rèn)為游戲服務(wù)器是根據(jù)地圖劃分的,不知道對(duì)不對(duì)?),角色數(shù)據(jù)傳到gameserver,同時(shí)客戶端根據(jù)這些數(shù)據(jù)進(jìn)入場(chǎng)景,gameserver得到信息同時(shí)告知publicserver(publicserver連DB做什么?我猜測(cè)可以獲取工會(huì)以及工會(huì)成員信息,對(duì)否?)
http://www.shnenglu.com/johndragon/archive/2008/04/10/46768.html
C++提供了
關(guān)鍵字explicit,可以阻止不應(yīng)該允許的經(jīng)過轉(zhuǎn)換構(gòu)造函數(shù)進(jìn)行的隱式轉(zhuǎn)換的發(fā)生。聲明為explicit的構(gòu)造函數(shù)不能在隱式轉(zhuǎn)換中使用。
C++中, 一個(gè)參數(shù)的構(gòu)造函數(shù)(或者除了第一個(gè)參數(shù)外其余參數(shù)都有默認(rèn)值的多參構(gòu)造函數(shù)), 承擔(dān)了兩個(gè)角色。 1 是個(gè)構(gòu)造器 2 是個(gè)默認(rèn)且隱含的類型轉(zhuǎn)換操作符。
所以, 有時(shí)候在我們寫下如 AAA = XXX, 這樣的代碼, 且恰好XXX的類型正好是AAA單參數(shù)構(gòu)造器的參數(shù)類型, 這時(shí)候編譯器就自動(dòng)調(diào)用這個(gè)構(gòu)造器, 創(chuàng)建一個(gè)AAA的對(duì)象。
這樣看起來好象很酷, 很方便。 但在某些情況下(見下面權(quán)威的例子), 卻違背了我們(程序員)的本意。 這時(shí)候就要在這個(gè)構(gòu)造器前面加上explicit修飾, 指定這個(gè)構(gòu)造器只能被明確的調(diào)用,使用, 不能作為類型轉(zhuǎn)換操作符被隱含的使用。 呵呵, 看來還是光明正大些比較好。
explicit構(gòu)造函數(shù)的作用
解析:
explicit構(gòu)造函數(shù)是用來防止隱式轉(zhuǎn)換的。請(qǐng)看下面的代碼:
class Test1
{
public:
Test1(int n) { num = n; } //普通構(gòu)造函數(shù)
private:
int num;
};
class Test2
{
public:
explicit Test2(int n) { num = n; } //explicit(顯式)構(gòu)造函數(shù)
private:
int num;
};
int main()
{
Test1 t1 = 12; //隱式調(diào)用其構(gòu)造函數(shù), 成功
Test2 t2 = 12; //編譯錯(cuò)誤,不能隱式調(diào)用其構(gòu)造函數(shù)
Test2 t3(12); //顯示調(diào)用成功
return 0;
}
Test1的構(gòu)造函數(shù)帶一個(gè)int型的參數(shù),代碼19行會(huì)隱式轉(zhuǎn)換成調(diào)用Test1的這個(gè)構(gòu)造函數(shù)。而Test2的構(gòu)造函數(shù)被聲明為explicit(顯式),這表示不能通過隱式轉(zhuǎn)換來調(diào)用這個(gè)構(gòu)造函數(shù),因此代碼20行會(huì)出現(xiàn)編譯錯(cuò)誤。
普通構(gòu)造函數(shù)能夠被隱式調(diào)用。而explicit構(gòu)造函數(shù)只能被顯示調(diào)用。
接下來是定義message屬性,一個(gè)message是包含了各種類型字段的聚集。有很多標(biāo)準(zhǔn)的變量類型可以使用,包 括:bool,int32,float,double和string。你也可以使用其他的message作為字段類型。正像例子中的Person包含了 PhoneNumber,而AddressBook包含了Persion。甚至可以在message內(nèi)部定義message,例 如:PhoneNumber就是在Persion里面定義的。你還可以定義enum類型,正像指定電話號(hào)碼類型的MOBILE、HOME、WORK。
其中“=1”,“=2”表示每個(gè)元素的標(biāo)識(shí)號(hào),它會(huì)用在二進(jìn)制編碼中對(duì)域的標(biāo)識(shí)。標(biāo)識(shí)號(hào)1-15由于使用時(shí)會(huì)比那些高的標(biāo)識(shí)號(hào)少一個(gè)字節(jié),從最優(yōu)化 角度考慮,可以將其使用在一些較常用的或repeated元素上,對(duì)于16以上的則使用在不常用的或optional的元素上。對(duì)于repeated的每 個(gè)元素都需要重復(fù)編碼該標(biāo)識(shí)號(hào),所以repeated的域進(jìn)行優(yōu)化來說是最顯示的。
每個(gè)字段必須提供一個(gè)修飾詞:
Ø required:表示字段必須提供,不能為空。否則message會(huì)被認(rèn)為是未初始化的,試圖build未初始化的message會(huì)拋出 RuntimeException。解析未初始化的message會(huì)拋出IOException。除此之外,一個(gè)required字段與optional 字段完全相同。
Ø optional:可選字段,可以設(shè)置也可以不設(shè)置。如果沒有設(shè)置,會(huì)設(shè)置一個(gè)缺省值。可以指定一個(gè)缺省值,正像電話號(hào)碼的type字段。否則,使用系統(tǒng) 的缺省值:數(shù)字類型缺省為0;字符類型缺省為空串;邏輯類型缺省為false;對(duì)于嵌入的message,缺省值通常是message的實(shí)例或原型。
Ø repeated:字段可以被重復(fù)(包括0),可等同于動(dòng)態(tài)數(shù)組或列表。其中存儲(chǔ)的值列表的順序是被保留的。
Required修飾的字段是永久性的,在使用該修飾符時(shí)一定要特別小心。如果在以后想要修改required域?yàn)閛ptional域時(shí)會(huì)出現(xiàn)問 題。對(duì)于訪問舊接口的用戶來說沒有該字段時(shí),將會(huì)認(rèn)為是不合法的訪問,將會(huì)被拒絕或丟棄。其中g(shù)oogle的一些工程師給出的建議是如果不是必須,就盡量 少用required修飾符。
一、什么是對(duì)齊,以及為什么要對(duì)齊:
1. 現(xiàn)代計(jì)算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對(duì)任何類型的變量的訪問可以從任何地址開始,但實(shí)際情況是在訪問特定變量的時(shí)候經(jīng)常在特定的內(nèi)存地址訪問,這就需要各類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對(duì)齊。
2. 對(duì)齊的作用和原因:各個(gè)硬件平臺(tái)對(duì)存儲(chǔ)空間的處理上有很大的不同。一些平臺(tái)對(duì)某些特定類型的數(shù)據(jù)只能從某些特定地址開始存取。其他平臺(tái)可能沒有這種情況, 但是最常見的是如果不按照適合其平臺(tái)的要求對(duì)數(shù)據(jù)存放進(jìn)行對(duì)齊,會(huì)在存取效率上帶來損失。比如有些平臺(tái)每次讀都是從偶地址開始,如果一個(gè)int型(假設(shè)為 32位)如果存放在偶地址開始的地方,那么一個(gè)讀周期就可以讀出,而如果存放在奇地址開始的地方,就可能會(huì)需要2個(gè)讀周期,并對(duì)兩次讀出的結(jié)果的高低 字節(jié)進(jìn)行拼湊才能得到該int數(shù)據(jù)。顯然在讀取效率上下降很多。這也是空間和時(shí)間的博弈。
二、對(duì)齊的實(shí)現(xiàn)
通常,我們寫程序的時(shí)候,不需要考慮對(duì)齊問題。編譯器會(huì)替我們選擇適合目標(biāo)平臺(tái)的對(duì)齊策略。當(dāng)然,我們也可以通知給編譯器傳遞預(yù)編譯指令而改變對(duì)指定數(shù)據(jù)的對(duì)齊方法。
但是,正因?yàn)槲覀円话悴恍枰P(guān)心這個(gè)問題,所以因?yàn)榫庉嬈鲗?duì)數(shù)據(jù)存放做了對(duì)齊,而我們不了解的話,常常會(huì)對(duì)一些問題感到迷惑。最常見的就是struct數(shù)據(jù)結(jié)構(gòu)的sizeof結(jié)果,出乎意料。為此,我們需要對(duì)對(duì)齊算法所了解。
對(duì)齊的算法:
由于各個(gè)平臺(tái)和編譯器的不同,現(xiàn)以本人使用的gcc version 3.2.2編譯器(32位x86平臺(tái))為例子,來討論編譯器對(duì)struct數(shù)據(jù)結(jié)構(gòu)中的各成員如何進(jìn)行對(duì)齊的。
設(shè)結(jié)構(gòu)體如下定義:
struct A {
int a;
char b;
short c;
};
結(jié)構(gòu)體A中包含了4字節(jié)長(zhǎng)度的int一個(gè),1字節(jié)長(zhǎng)度的char一個(gè)和2字節(jié)長(zhǎng)度的short型數(shù)據(jù)一個(gè)。所以A用到的空間應(yīng)該是7字節(jié)。但是因?yàn)榫幾g器要對(duì)數(shù)據(jù)成員在空間上進(jìn)行對(duì)齊。
所以使用sizeof(strcut A)值為8。
現(xiàn)在把該結(jié)構(gòu)體調(diào)整成員變量的順序。
struct B {
char b;
int a;
short c;
};
這時(shí)候同樣是總共7個(gè)字節(jié)的變量,但是sizeof(struct B)的值卻是12。
下面我們使用預(yù)編譯指令#pragma pack (value)來告訴編譯器,使用我們指定的對(duì)齊值來取代缺省的。
#pragma pack (2) /*指定按2字節(jié)對(duì)齊*/
struct C {
char b;
int a;
short c;
};
#pragma pack () /*取消指定對(duì)齊,恢復(fù)缺省對(duì)齊*/
sizeof(struct C)值是8。
修改對(duì)齊值為1:
#pragma pack (1) /*指定按1字節(jié)對(duì)齊*/
struct D {
char b;
int a;
short c;
};
#pragma pack () /*取消指定對(duì)齊,恢復(fù)缺省對(duì)齊*/
sizeof(struct D)值為7。
對(duì)于char型數(shù)據(jù),其自身對(duì)齊值為1,對(duì)于short型為2,對(duì)于int,float,double類型,其自身對(duì)齊值為4,單位字節(jié)。
這里面有四個(gè)概念值:
1)數(shù)據(jù)類型自身的對(duì)齊值:就是上面交代的基本數(shù)據(jù)類型的自身對(duì)齊值。
2)指定對(duì)齊值:#pragma pack (value)時(shí)的指定對(duì)齊值value。
3)結(jié)構(gòu)體或者類的自身對(duì)齊值:其成員中自身對(duì)齊值最大的那個(gè)值。
4)數(shù)據(jù)成員、結(jié)構(gòu)體和類的有效對(duì)齊值:自身對(duì)齊值和指定對(duì)齊值中較小的那個(gè)值。
有了這些值,我們就可以很方便的來討論具體數(shù)據(jù)結(jié)構(gòu)的成員和其自身的對(duì)齊方式。有效對(duì)齊值N是最終用來決定數(shù)據(jù)存放地址方式的值,最重要。有效對(duì)齊N,就 是表示“對(duì)齊在N上”,也就是說該數(shù)據(jù)的"存放起始地址%N=0".而數(shù)據(jù)結(jié)構(gòu)中的數(shù)據(jù)變量都是按定義的先后順序來排放的。第一個(gè)數(shù)據(jù)變量的起始地址就是 數(shù)據(jù)結(jié)構(gòu)的起始地址。結(jié)構(gòu)體的成員變量要對(duì)齊排放,結(jié)構(gòu)體本身也要根據(jù)自身的有效對(duì)齊值圓整(就是結(jié)構(gòu)體成員變量占用總長(zhǎng)度需要是對(duì)結(jié)構(gòu)體有效對(duì)齊值的整 數(shù)倍,結(jié)合下面例子理解)。這樣就不難理解上面的幾個(gè)例子的值了。
例子分析:
分析例子B;
struct B {
char b;
int a;
short c;
};
假設(shè)B從地址空間0x0000開始排放。該例子中沒有定義指定對(duì)齊值,在筆者環(huán)境下,該值默認(rèn)為4。第一個(gè)成員變量b的自身對(duì)齊值是1,比指定或者默認(rèn)指 定對(duì)齊值4小,所以其有效對(duì)齊值為1,所以其存放地址0x0000符合0x0000%1=0.第二個(gè)成員變量a,其自身對(duì)齊值為4,所以有效對(duì)齊值也為 4,所以只能存放在起始地址為0x0004到0x0007這四個(gè)連續(xù)的字節(jié)空間中,復(fù)核0x0004%4=0,且緊靠第一個(gè)變量。第三個(gè)變量c,自身對(duì)齊 值為2,所以有效對(duì)齊值也是2,可以存放在0x0008到0x0009這兩個(gè)字節(jié)空間中,符合0x0008%2=0。所以從0x0000到0x0009存 放的都是B內(nèi)容。再看數(shù)據(jù)結(jié)構(gòu)B的自身對(duì)齊值為其變量中最大對(duì)齊值(這里是b)所以就是4,所以結(jié)構(gòu)體的有效對(duì)齊值也是4。根據(jù)結(jié)構(gòu)體圓整的要求, 0x0009到0x0000=10字節(jié),(10+2)%4=0。所以0x0000A到0x000B也為結(jié)構(gòu)體B所占用。故B從0x0000到0x000B 共有12個(gè)字節(jié),sizeof(struct B)=12;
同理,分析上面例子C:
#pragma pack (2) /*指定按2字節(jié)對(duì)齊*/
struct C {
char b;
int a;
short c;
};
#pragma pack () /*取消指定對(duì)齊,恢復(fù)缺省對(duì)齊*/
第一個(gè)變量b的自身對(duì)齊值為1,指定對(duì)齊值為2,所以,其有效對(duì)齊值為1,假設(shè)C從0x0000開始,那么b存放在0x0000,符合0x0000%1= 0;第二個(gè)變量,自身對(duì)齊值為4,指定對(duì)齊值為2,所以有效對(duì)齊值為2,所以順序存放在0x0002、0x0003、0x0004、0x0005四個(gè)連續(xù) 字節(jié)中,符合0x0002%2=0。第三個(gè)變量c的自身對(duì)齊值為2,所以有效對(duì)齊值為2,順序存放
在0x0006、0x0007中,符合0x0006%2=0。所以從0x0000到0x00007共八字節(jié)存放的是C的變量。又C的自身對(duì)齊值為4,所以 C的有效對(duì)齊值為2。又8%2=0,C只占用0x0000到0x0007的八個(gè)字節(jié)。所以sizeof(struct C)=8.
有 了以上的解釋,相信你對(duì)C語言的字節(jié)對(duì)齊概念應(yīng)該有了清楚的認(rèn)識(shí)了吧。在網(wǎng)絡(luò)程序中,掌握這個(gè)概念可是很重要的喔,在不同平臺(tái)之間(比如在Windows 和Linux之間)傳遞2進(jìn)制流(比如結(jié)構(gòu)體),那么在這兩個(gè)平臺(tái)間必須要定義相同的對(duì)齊方式,不然莫名其妙的出了一些錯(cuò),可是很難排查的哦^_^。
ProtocolBuffers2.4.1應(yīng)用說明(一)
2012-02-03 12:07
客方的ProtocolBuffers 詳細(xì)說明,可以下載最新版的ProtocolBuffers包。
我所下載的包是:protobuf-2.4.1.tar.bz2 、 protoc-2.4.1-win32.zip
ProtocolBuffers 首頁:http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/overview.html
protobuf-2.4.1.tar.bz2 是源碼包
protoc-2.4.1-win32.zip 是編譯 .proto 文件的編譯器
本文使用 ProtocolBuffers 的環(huán)境
操作系統(tǒng): windows 7 64位
開發(fā)工具:Visual studio 2008
開發(fā)語言:C++、MFC類庫
第一步: 編譯protobuf-2.4.1工程
說明:編譯protobuf-2.4.1工程后生成 libprotobuf.lib, libprotobuf.lib 會(huì)在自已的工程文件中用到這個(gè)庫文件
1)當(dāng)前的目錄結(jié)構(gòu)為:
E:\ProtocolBuffers\ 此目錄結(jié)構(gòu)下有兩個(gè)包 protobuf-2.4.1.tar.bz2 、protoc-2.4.1-win32.zip
2)解壓 protobuf-2.4.1.tar.bz2 包
會(huì)生 E:\ProtocolBuffers\protobuf-2.4.1\protobuf-2.4.1目錄結(jié)構(gòu)
調(diào)整后的目錄結(jié)構(gòu)為:E:\ProtocolBuffers\protobuf-2.4.1目錄結(jié)構(gòu),便于應(yīng)用。
3)VS2008編譯工程
找到 E:\ProtocolBuffers\protobuf-2.4.1\vsprojects\protobuf.sln文件。
用VS2008 打開,然后編譯整個(gè)功程,很順利的編譯完整個(gè)功程。
如圖所示:

編譯完成后會(huì)在E:\ProtocolBuffers\protobuf-2.4.1\vsprojects\Debug 目錄結(jié)構(gòu)中生成libprotobuf.lib庫文件。
4) 如果出現(xiàn)問題:
可以閱讀 vsprojects\readme.txt 說明文檔。
第二步:編寫 .proto 文件
1)在目錄 E:\ProtocolBuffers\protobuf-2.4.1\examples 中有個(gè)示例
可以先按官方的文檔來熟悉一下。
2) 編寫 .proto 文件
自已編寫的 shapeobject.proto 文件
package candee;
option java_package = "com.example.candee";
option java_outer_classname = "ShapeObjectProto";
message DrawInfoPB {
message ColorVal {
required int32r = 1;//int32 unsigned short
required int32g = 2;
required int32b = 3;
}
required int32toolbarState = 1;// TOOLBAR_STATE
required ColorValpenColor = 2;// 筆的顏色
required int32penLineWidth = 3;// 用戶設(shè)置畫筆的寬度
required ColorValwordColor = 4;// 字的顏色
required int32wordLineWidth = 5;// 用戶設(shè)置字的寬度
required ColorValgraphColor = 6;// 圖形的顏色
required int32graph = 7;// 圖形
required int32graphLineWidth = 8;// 繪制圖形的線寬
}
message ShapeObjectPB {
required DrawInfoPB drawInfoPB = 1;// 繪畫信息
message DrawPointPB {
required int32 x1 = 1;
required int32 y1 = 2;
required int32 X2 = 3;
required int32 y2 = 4;
}
repeated DrawPointPB drawPointPB = 2;// 繪畫坐標(biāo)
optional string textPB = 3;// 編輯框文字信息
}
message DataPB {
repeated ShapeObjectPB shapeObjectPB = 1;
}
第三步 編譯 shapeobject.proto 文件,生成C++源文件
1)解壓 E:\ProtocolBuffers\protoc-2.4.1-win32.zip
會(huì)生成 E:\ProtocolBuffers\protoc-2.4.1-win32\protoc.exe 編譯文件。
2) 將 protoc.exe 考貝到 shapeobject.proto文件同一級(jí)目錄中。
本目錄為 E:\ProtocolBuffers\protobuf-2.4.1\examples
3)命令執(zhí)行protoc 文件
在\examples\ 新建一個(gè)目錄為 1\用來保存生成的C++源文件
在命令行下,執(zhí)行protoc --cpp_out=1 shapeobject.proto
如圖所示:

4)生成的C++源文件
在E:\ProtocolBuffers\protobuf-2.4.1\examples\1 目錄中保存生成的文件
生成的文件:shapeobject.pb.cc shapeobject.pb.h
將這兩個(gè)文件添加到你的功程中,就可以用戶ProtocolBuffer了 |