在游戲服務器開發中, 網絡通信是很關鍵的, 而在網絡通信中,數據的同步是關鍵, 在我的上一篇博客(
http://www.shnenglu.com/zhengxf/archive/2010/07/08/119737.html)中談的了數據如何同步的問題, 其中提到了兩點 1.數據發送給誰. 2.網絡延遲的話,如何同步.
上次主要談了網絡延遲時的同步問題,今天我們主要談論數據發送給誰的問題, 要談論這個問題,我們就不的不談論: 地圖, NPC, 玩家他們三個之間的關系.
其實地圖是個靜態的數據集合, 其中主要包含了地形信息和物品信息,這里的物品指的是靜態的,所以它是不需要進行同步的,也就是每個玩家在同一個地圖上看到信息是一樣的,所以這些信息有客戶端的場景管理器管理. 不進行網絡通信.
NPC: NPC主要分兩種 一種是靜態的NPC,如玩家獲取任務的NPC, 另外玩家升級打的怪, 也是NPC. 靜態的NPC有屬性和功能,但不需要同步, 動態的NPC(如怪)需要進行網絡同步,這里我們就有一個問題, 動態的NPC要將它的信息發送給誰呢?任何一個涉及到網絡通信的NPC和玩家都有一個共同的性質就是它有可見范圍,假如說一個玩家在十米范圍內可見,那么他就回將信息發送給距離十米以內的玩家和NPC, 這里有含有兩個問題: 一: 十米以外的NPC怎么辦, 二:怎么獲取十米以內的發送用戶。有兩種解決辦法:一是在場景中搜索找到要發送的用戶信息,告訴服務器讓服務器來發,二是發送自己的信息到服務器,服務器搜索NPC列表,和玩家列表,來發送,我覺的后著更合理。
玩家: 玩家的同步主要是玩家和玩家的同步, 如玩家每前進一步, 就向服務器發一條消息. 服務器在玩家列表中查找到范圍之內的其他玩家信息,并發送消息,這里有一個問題,是玩家要不要將消息發送給NPC?因為這種NPC也可以理解為機器人,它可以感知到敵人就在附近, 本身就在服務上, 所以不用發送消息, 但是NPC在感受到玩家的時候要向它(NPC) 可見范圍內的玩家發送消息.
接下來還有一個問題,是發送什么樣的消息的問題,因為針對NPC,和玩家,并不是所有的消息都發,而是有選擇向的發,這個問題我們下次討論。
__stdcall和__cdecl兩者的區別;
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
#define cdecl _cdecl
#ifndef CDECL
#define CDECL _cdecl
#endif
幾乎我們寫的每一個WINDOWS API函數都是__stdcall類型的,首先,需要了解兩者之間的區別: WINDOWS的函數調用時需要用到棧(STACK,一種先入后出的存儲結構)。當函數調用完成后,棧需要清除,這里就是問題的關鍵,如何清除??如果我們的函數使用了_cdecl,那么棧的清除工作是由調用者,用COM的術語來講就是客戶來完成的。這樣帶來了一個棘手的問題,不同的編譯器產生棧的方式不盡相同,那么調用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的問題就解決了,函數自己解決清除工作。所以,在跨(開發)平臺的調用中,我們都使用__stdcall(雖然有時是以 WINAPI的樣子出現)。那么為什么還需要_cdecl呢?當我們遇到這樣的函數如fprintf()它的參數是可變的,不定長的,被調用者事先無法知道參數的長度,事后的清除工作也無法正常的進行,因此,這種情況我們只能使用_cdecl。到這里我們有一個結論,如果你的程序中沒有涉及可變參數,最好使用__stdcall關鍵字。
2.
__cdecl,__stdcall是聲明的函數調用協議.主要是傳參和彈棧方面的不同.一般c++用的是__cdecl,windows里大都用的是__stdcall(API)
__cdecl 是C/C++和MFC程序默認使用的調用約定,也可以在函數聲明時加上__cdecl關鍵字來手工指定。采用__cdecl約定時,函數參數按照從右到左的順序入棧,并且由調用函數者把參數彈出棧以清理堆棧。因此,實現可變參數的函數只能使用該調用約定。由于每一個使用__cdecl約定的函數都要包含清理堆棧的代碼,所以產生的可執行文件大小會比較大。__cdecl可以寫成_cdecl。
__stdcall調用約定用于調用Win32 API函數。采用__stdcall約定時,函數參數按照從右到左的順序入棧,被調用的函數在返回前清理傳送參數的棧,函數參數個數固定。由于函數體本身知道傳進來的參數個數,因此被調用的函數可以在返回前用一條ret n指令直接清理傳遞參數的堆棧。__stdcall可以寫成_stdcall。
__fastcall 約定用于對性能要求非常高的場合。__fastcall約定將函數的從左邊開始的兩個大小不大于4個字節(DWORD)的參數分別放在ECX和EDX寄存器,其余的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的堆棧。__fastcall可以寫成_fastcall
3.
__stdcall:
_stdcall 調用約定相當于16位動態庫中經常使用的PASCAL調用約定。
在32位的VC++5.0中PASCAL調用約定不再被支持(實際上它已被定義為 __stdcall。除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall調用約定。兩者實質上是一致的,即函數的參數自右向左通過棧傳遞,被調用的函數在返回前清理傳送參數的內存棧,但不同的是函數名的修飾部分(關于函數名的修飾部分在后面將詳細說明)。
_stdcall是Pascal程序的缺省調用方式,通常用于Win32 Api中,函數采用從右到左的壓棧方式,自己在退出時清空堆棧。VC將函數編譯后會在函數名前面加上下劃線前綴,在函數名后加上"@"和參數的字節數。
_cdecl:
_cdecl c調用約定, 按從右至左的順序壓參數入棧,由調用者把參數彈出棧。對于傳送參數的內存棧是由調用者來維護的(正因為如此,實現可變參數的函數只能使用該調用約定)。另外,在函數名修飾約定方面也有所不同。
_cdecl是C和C++程序的缺省調用方式。每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大。函數采用從右到左的壓棧方式。VC將函數編譯后會在函數名前面加上下劃線前綴。是MFC缺省調用約定。
__fastcall:
__fastcall調用約定是"人"如其名,它的主要特點就是快,因為它是通過寄存器來傳送參數的(實際上,它用ECX和EDX傳送前兩個雙字(DWORD)或更小的參數,剩下的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的內存棧),在函數名修飾約定方面,它和前兩者均不同。
_fastcall方式的函數采用寄存器傳遞參數,VC將函數編譯后會在函數名前面加上"@"前綴,在函數名后加上"@"和參數的字節數。
thiscall:
thiscall僅僅應用于"C++"成員函數。this指針存放于CX寄存器,參數從右到左壓。thiscall不是關鍵詞,因此不能被程序員指定。
naked call:
采用1-4的調用約定時,如果必要的話,進入函數時編譯器會產生代碼來保存ESI,EDI,EBX,EBP寄存器,退出函數時則產生代碼恢復這些寄存器的內容。
naked call不產生這樣的代碼。naked call不是類型修飾符,故必須和_declspec共同使用。
另附:
關鍵字 __stdcall、__cdecl和__fastcall可以直接加在要輸出的函數前,也可以在編譯環境的Setting...\C/C++ \Code Generation項選擇。當加在輸出函數前的關鍵字與編譯環境中的選擇不同時,直接加在輸出函數前的關鍵字有效。它們對應的命令行參數分別為/Gz、 /Gd和/Gr。缺省狀態為/Gd,即__cdecl。
要完全模仿PASCAL調用約定首先必須使用__stdcall調用約定,至于函數名修飾約定,可以通過其它方法模仿。還有一個值得一提的是WINAPI宏,Windows.h支持該宏,它可以將出函數翻譯成適當的調用約定,在WIN32中,它被定義為__stdcall。使用WINAPI宏可以創建自己的APIs。
名字修飾約定
1、修飾名(Decoration name)
“C” 或者“C++”函數在內部(編譯和鏈接)通過修飾名識別。修飾名是編譯器在編譯函數定義或者原型時生成的字符串。有些情況下使用函數的修飾名是必要的,如在模塊定義文件里頭指定輸出“C++”重載函數、構造函數、析構函數,又如在匯編代碼里調用“C””或“C++”函數等。
修飾名由函數名、類名、調用約定、返回類型、參數等共同決定。
2、名字修飾約定隨調用約定和編譯種類(C或C++)的不同而變化。函數名修飾約定隨編譯種類和調用約定的不同而不同,下面分別說明。
a、C編譯時函數名修飾約定規則:
__cdecl調用約定僅在輸出函數名前加上一個下劃線前綴,格式為_functionname。
__fastcall調用約定在輸出函數名前加上一個“@”符號,后面也是一個“@”符號和其參數的字節數,格式為@functionname@number。
它們均不改變輸出函數名中的字符大小寫,這和PASCAL調用約定不同,PASCAL約定輸出的函數名無任何修飾且全部大寫。
b、C++編譯時函數名修飾約定規則:
__stdcall調用約定:
1、以“?”標識函數名的開始,后跟函數名;
2、函數名后面以
“@@YG”標識參數表的開始,后跟參數表;
3、參數表以代號表示:
X--void ,
D--char,
E--unsigned char,
F--short,
H--int,
I--unsigned int,
J--long,
K--unsigned long,
M--float,
N--double,
_N--bool,
....
PA--表示指針,后面的代號表明指針類型,如果相同類型的指針連續出現,以“0”代替,一個“0”代表一次重復;
4、參數表的第一項為該函數的返回值類型,其后依次為參數的數據類型,指針標識在其所指數據類型前;
5、參數表后以
“@Z”標識整個名字的結束,如果該函數無參數,則以“Z”標識結束。
__cdecl調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的
“@@YG”變為
“@@YA”。
__fastcall調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的
“@@YG”變為
“@@YI”。
VC++對函數的省缺聲明是“__cedcl“,將只能被C/C++調用.
CB在輸出函數聲明時使用4種修飾符號
//__cdecl
cb的默認值,它會在輸出函數名前加_,并保留此函數名不變,參數按照從右到左的順序依次傳遞給棧,也可以寫成_cdecl和cdecl形式。
//__fastcall
她修飾的函數的參數將盡肯呢感地使用寄存器來處理,其函數名前加@,參數按照從左到右的順序壓棧;
//__pascal
它說明的函數名使用Pascal格式的命名約定。這時函數名全部大寫。參數按照從左到右的順序壓棧;
//__stdcall
使用標準約定的函數名。函數名不會改變。使用__stdcall修飾時。參數按照由右到左的順序壓棧,也可以是_stdcall;
VC++對函數的省缺聲明是"__cedcl",將只能被C/C++調用.
注意:
1、_beginthread需要__cdecl的線程函數地址,_beginthreadex和CreateThread需要__stdcall的線程函數地址。
2、一般WIN32的函數都是__stdcall。而且在Windef.h中有如下的定義:
#define CALLBACK __stdcall
#define WINAPI __stdcall
3、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);
typedef int (__cdecl*FunPointer)(int a, int b);
修飾符的書寫順序如上。
4、extern "C"的作用:如果Add(int a, int b)是在c語言編譯器編譯,而在c++文件使用,則需要在c++文件中聲明:extern "C" Add(int a, int b),因為c編譯器和c++編譯器對函數名的解釋不一樣(c++編譯器解釋函數名的時候要考慮函數參數,這樣是了方便函數重載,而在c語言中不存在函數重載的問題),使用extern "C",實質就是告訴c++編譯器,該函數是c庫里面的函數。如果不使用extern "C"則會出現鏈接錯誤。
一般象如下使用:
#ifdef _cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C extern
#endif
#ifdef _cplusplus
extern "C"{
#endif
EXTERN_C int func(int a, int b);
#ifdef _cplusplus
}
#endif
5、MFC提供了一些宏,可以使用AFX_EXT_CLASS來代替__declspec(DLLexport),并修飾類名,從而導出類,AFX_API_EXPORT來修飾函數,AFX_DATA_EXPORT來修飾變量
AFX_CLASS_IMPORT:__declspec(DLLexport)
AFX_API_IMPORT:__declspec(DLLexport)
AFX_DATA_IMPORT:__declspec(DLLexport)
AFX_CLASS_EXPORT:__declspec(DLLexport)
AFX_API_EXPORT:__declspec(DLLexport)
AFX_DATA_EXPORT:__declspec(DLLexport)
AFX_EXT_CLASS:#ifdef _AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
6、DLLMain負責初始化(Initialization)和結束 (Termination)工作,每當一個新的進程或者該進程的新的線程訪問DLL時,或者訪問DLL的每一個進程或者線程不再使用DLL或者結束時,都會調用DLLMain。但是,使用TerminateProcess或TerminateThread結束進程或者線程,不會調用DLLMain。
7、一個DLL在內存中只有一個實例
DLL程序和調用其輸出函數的程序的關系:
1)、DLL與進程、線程之間的關系
DLL模塊被映射到調用它的進程的虛擬地址空間。
DLL使用的內存從調用進程的虛擬地址空間分配,只能被該進程的線程所訪問。
DLL的句柄可以被調用進程使用;調用進程的句柄可以被DLL使用。
DLLDLL可以有自己的數據段,但沒有自己的堆棧,使用調用進程的棧,與調用它的應用程序相同的堆棧模式。
2)、關于共享數據段
DLL定義的全局變量可以被調用進程訪問;DLL可以訪問調用進程的全局數據。使用同一 DLL的每一個進程都有自己的DLL全局變量實例。如果多個線程并發訪問同一變量,則需要使用同步機制;對一個DLL的變量,如果希望每個使用DLL的線程都有自己的值,則應該使用線程局部存儲(TLS,Thread Local Strorage)。
于網絡所帶來的諸多不安全因素使得網絡使用者不得不采取相應的網絡安全對策。為了堵塞安全漏洞和提供安全的通信服務,必須運用一定的技術來對網絡進行安全建設,這已為廣大網絡開發商和網絡用戶所共識。
現今主要的網絡安全技術有以下幾種:
一、加密路由器(Encrypting Router)技術
加密路由器把通過路由器的內容進行加密和壓縮,然后讓它們通過不安全的網絡進行傳輸,并在目的端進行解壓和解密。
二、安全內核(Secured Kernel)技術
人們開始在操作系統的層次上考慮安全性,嘗試把系統內核中可能引起安全性問題的部分從內核中剔除出去,從而使系統更安全。如S olaris操作系統把靜態的口令放在一個隱含文件中, 使系統的安全性增強。
三、網絡地址轉換器(Network Address Translater)
網絡地址轉換器也稱為地址共享器(Address Sharer)或地址映射器,初衷是為了解決IP 地址不足,現多用于網絡安全。內部主機向外部主機連接時,使用同一個IP地址;相反地,外部主機要向內部主機連接時,必須通過網關映射到內部主機上。它使外部網絡看不到內部網絡, 從而隱藏內部網絡,達到保密作用。
數據加密(Data Encryption)技術
所謂加密(Encryption)是指將一個信息(或稱明文--plaintext) 經過加密鑰匙(Encrypt ionkey)及加密函數轉換,變成無意義的密文( ciphertext),而接收方則將此密文經過解密函數、解密鑰匙(Decryti on key)還原成明文。加密技術是網絡安全技術的基石。
數據加密技術要求只有在指定的用戶或網絡下,才能解除密碼而獲得原來的數據,這就需要給數據發送方和接受方以一些特殊的信息用于加解密,這就是所謂的密鑰。其密鑰的值是從大量的隨機數中選取的。按加密算法分為專用密鑰和公開密鑰兩種。
專用密鑰,又稱為對稱密鑰或單密鑰,加密時使用同一個密鑰,即同一個算法。如DES和MIT的Kerberos算法。單密鑰是最簡單方式,通信雙方必須交換彼此密鑰,當需給對方發信息時,用自己的加密密鑰進行加密,而在接收方收到數據后,用對方所給的密鑰進行解密。這種方式在與多方通信時因為需要保存很多密鑰而變得很復雜,而且密鑰本身的安全就是一個問題。
DES是一種數據分組的加密算法,它將數據分成長度為6 4位的數據塊,其中8位用作奇偶校驗,剩余的56位作為密碼的長度。第一步將原文進行置換,得到6 4位的雜亂無章的數據組;第二步將其分成均等兩段 ;第三步用加密函數進行變換,并在給定的密鑰參數條件下,進行多次迭代而得到加密密文。
公開密鑰,又稱非對稱密鑰,加密時使用不同的密鑰,即不同的算法,有一把公用的加密密鑰,有多把解密密鑰,如RSA算法。
在計算機網絡中,加密可分為"通信加密"(即傳輸過程中的數據加密)和"文件加密"(即存儲數據加密)。通信加密又有節點加密、鏈路加密和端--端加密3種。
①節點加密,從時間坐標來講,它在信息被傳入實際通信連接點 (Physical communication link)之前進行;從OSI 7層參考模型的坐標 (邏輯空間)來講,它在第一層、第二層之間進行; 從實施對象來講,是對相鄰兩節點之間傳輸的數據進行加密,不過它僅對報文加密,而不對報頭加密,以便于傳輸路由的選擇。
②鏈路加密(Link Encryption),它在數據鏈路層進行,是對相鄰節點之間的鏈路上所傳輸的數據進行加密,不僅對數據加密還對報頭加密。
③端--端加密(End-to-End Encryption),它在第六層或第七層進行 ,是為用戶之間傳送數據而提供的連續的保護。在始發節點上實施加密,在中介節點以密文形式傳輸,最后到達目的節點時才進行解密,這對防止拷貝網絡軟件和軟件泄漏也很有效。
在OSI參考模型中,除會話層不能實施加密外,其他各層都可以實施一定的加密措施。但通常是在最高層上加密,即應用層上的每個應用都被密碼編碼進行修改,因此能對每個應用起到保密的作用,從而保護在應用層上的投資。假如在下面某一層上實施加密,如TCP層上,就只能對這層起到保護作用。
值得注意的是,能否切實有效地發揮加密機制的作用,關鍵的問題在于密鑰的管理,包括密鑰的生存、分發、安裝、保管、使用以及作廢全過程。
(1)數字簽名
公開密鑰的加密機制雖提供了良好的保密性,但難以鑒別發送者, 即任何得到公開密鑰的人都可以生成和發送報文。數字簽名機制提供了一種鑒別方法,以解決偽造、抵賴、冒充和篡改等問題。
數字簽名一般采用不對稱加密技術(如RSA),通過對整個明文進行某種變換,得到一個值,作為核實簽名。接收者使用發送者的公開密鑰對簽名進行解密運算,如其結果為明文,則簽名有效,證明對方的身份是真實的。當然,簽名也可以采用多種方式,例如,將簽名附在明文之后。數字簽名普遍用于銀行、電子貿易等。
數字簽名不同于手寫簽字:數字簽名隨文本的變化而變化,手寫簽字反映某個人個性特征, 是不變的;數字簽名與文本信息是不可分割的,而手寫簽字是附加在文本之后的,與文本信息是分離的。
(2)Kerberos系統
Kerberos系統是美國麻省理工學院為Athena工程而設計的,為分布式計算環境提供一種對用戶雙方進行驗證的認證方法。
它的安全機制在于首先對發出請求的用戶進行身份驗證,確認其是否是合法的用戶;如是合法的用戶,再審核該用戶是否有權對他所請求的服務或主機進行訪問。從加密算法上來講,其驗證是建立在對稱加密的基礎上的。
Kerberos系統在分布式計算環境中得到了廣泛的應用(如在Notes 中),這是因為它具有如下的特點:
①安全性高,Kerberos系統對用戶的口令進行加密后作為用戶的私鑰,從而避免了用戶的口令在網絡上顯示傳輸,使得竊聽者難以在網絡上取得相應的口令信息;
②透明性高,用戶在使用過程中,僅在登錄時要求輸入口令,與平常的操作完全一樣,Ker beros的存在對于合法用戶來說是透明的;
③可擴展性好,Kerberos為每一個服務提供認證,確保應用的安全。
Kerberos系統和看電影的過程有些相似,不同的是只有事先在Ker beros系統中登錄的客戶才可以申請服務,并且Kerberos要求申請到入場券的客戶就是到TGS(入場券分配服務器)去要求得到最終服務的客戶。
Kerberos的認證協議過程如圖二所示。
Kerberos有其優點,同時也有其缺點,主要如下:
①、Kerberos服務器與用戶共享的秘密是用戶的口令字,服務器在回應時不驗證用戶的真實性,假設只有合法用戶擁有口令字。如攻擊者記錄申請回答報文,就易形成代碼本攻擊。
②、Kerberos服務器與用戶共享的秘密是用戶的口令字,服務器在回應時不驗證用戶的真實性,假設只有合法用戶擁有口令字。如攻擊者記錄申請回答報文,就易形成代碼本攻擊。
③、AS和TGS是集中式管理,容易形成瓶頸,系統的性能和安全也嚴重依賴于AS和TGS的性能和安全。在AS和TGS前應該有訪問控制,以增強AS和TGS的安全。
④、隨用戶數增加,密鑰管理較復雜。Kerberos擁有每個用戶的口令字的散列值,AS與TGS 負責戶間通信密鑰的分配。當N個用戶想同時通信時,仍需要N*(N-1)/2個密鑰
( 3 )、PGP算法
PGP(Pretty Good Privacy)是作者hil Zimmermann提出的方案, 從80年代中期開始編寫的。公開密鑰和分組密鑰在同一個系統中,公開密鑰采用RSA加密算法,實施對密鑰的管理;分組密鑰采用了IDEA算法,實施對信息的加密。
PGP應用程序的第一個特點是它的速度快,效率高;另一個顯著特點就是它的可移植性出色,它可以在多種操作平臺上運行。PGP主要具有加密文件、發送和接收加密的E-mail、數字簽名等。
(4)、PEM算法
保密增強郵件(Private Enhanced Mail,PEM),是美國RSA實驗室基于RSA和DES算法而開發的產品,其目的是為了增強個人的隱私功能, 目前在Internet網上得到了廣泛的應用,專為E-mail用戶提供如下兩類安全服務:
對所有報文都提供諸如:驗證、完整性、防抵 賴等安全服務功能; 提供可選的安全服務功能,如保密性等。
PEM對報文的處理經過如下過程:
第一步,作規范化處理:為了使PEM與MTA(報文傳輸代理)兼容,按S MTP協議對報文進行規范化處理;
第二步,MIC(Message Integrity Code)計算;
第三步,把處理過的報文轉化為適于SMTP系統傳輸的格式。
身份驗證技術
身份識別(Identification)是指定用戶向系統出示自己的身份證明過程。身份認證(Authertication)是系統查核用戶的身份證明的過程。人們常把這兩項工作統稱為身份驗證(或身份鑒別),是判明和確認通信雙方真實身份的兩個重要環節。
Web網上采用的安全技術
在Web網上實現網絡安全一般有SHTTP/HTTP和SSL兩種方式。
(一)、SHTTP/HTTP
SHTTP/HTTP可以采用多種方式對信息進行封裝。封裝的內容包括加密、簽名和基于MAC 的認證。并且一個消息可以被反復封裝加密。此外,SHTTP還定義了包頭信息來進行密鑰傳輸、認證傳輸和相似的管理功能。SHTTP可以支持多種加密協議,還為程序員提供了靈活的編程環境。
SHTTP并不依賴于特定的密鑰證明系統,它目前支持RSA、帶內和帶外以及Kerberos密鑰交換。
(二)、SSL(安全套層) 安全套接層是一種利用公開密鑰技術的工業標準。SSL廣泛應用于Intranet和Internet 網,其產品包括由Netscape、Microsoft、IBM 、Open Market等公司提供的支持SSL的客戶機和服務器,以及諸如Apa che-SSL等產品。
SSL提供三種基本的安全服務,它們都使用公開密鑰技術。
①信息私密,通過使用公開密鑰和對稱密鑰技術以達到信息私密。SSL客戶機和SSL服務器之間的所有業務使用在SSL握手過程中建立的密鑰和算法進行加密。這樣就防止了某些用戶通過使用IP packet sniffer工具非法竊聽。盡管packet sniffer仍能捕捉到通信的內容, 但卻無法破譯。 ②信息完整性,確保SSL業務全部達到目的。如果Internet成為可行的電子商業平臺,應確保服務器和客戶機之間的信息內容免受破壞。SSL利用機密共享和hash函數組提供信息完整性服務。③相互認證,是客戶機和服務器相互識別的過程。它們的識別號用公開密鑰編碼,并在SSL握手時交換各自的識別號。為了驗證證明持有者是其合法用戶(而不是冒名用戶),SSL要求證明持有者在握手時對交換數據進行數字式標識。證明持有者對包括證明的所有信息數據進行標識以說明自己是證明的合法擁有者。這樣就防止了其他用戶冒名使用證明。證明本身并不提供認證,只有證明和密鑰一起才起作用。 ④SSL的安全性服務對終端用戶來講做到盡可能透明。一般情況下,用戶只需單擊桌面上的一個按鈕或聯接就可以與SSL的主機相連。與標準的HTTP連接申請不同,一臺支持SSL的典型網絡主機接受SSL連接的默認端口是443而不是80。
當客戶機連接該端口時,首先初始化握手協議,以建立一個SSL對話時段。握手結束后,將對通信加密,并檢查信息完整性,直到這個對話時段結束為止。每個SSL對話時段只發生一次握手。相比之下,HTTP 的每一次連接都要執行一次握手,導致通信效率降低。一次SSL握手將發生以下事件:
1.客戶機和服務器交換X.509證明以便雙方相互確認。這個過程中可以交換全部的證明鏈,也可以選擇只交換一些底層的證明。證明的驗證包括:檢驗有效日期和驗證證明的簽名權限。
2.客戶機隨機地產生一組密鑰,它們用于信息加密和MAC計算。這些密鑰要先通過服務器的公開密鑰加密再送往服務器。總共有四個密鑰分別用于服務器到客戶機以及客戶機到服務器的通信。
3.信息加密算法(用于加密)和hash函數(用于確保信息完整性)是綜合在一起使用的。Netscape的SSL實現方案是:客戶機提供自己支持的所有算法清單,服務器選擇它認為最有效的密碼。服務器管理者可以使用或禁止某些特定的密碼。
代理服務
在 Internet 中廣泛采用代理服務工作方式, 如域名系統(DNS), 同時也有許多人把代理服務看成是一種安全性能。
從技術上來講代理服務(Proxy Service)是一種網關功能,但它的邏輯位置是在OSI 7層協議的應用層之上。
代理(Proxy)使用一個客戶程序,與特定的中間結點鏈接,然后中間結點與期望的服務器進行實際鏈接。與應用網關型防火墻所不同的是,使用這類防火墻時外部網絡與內部網絡之間不存在直接連接,因此 ,即使防火墻產生了問題,外部網絡也無法與被保護的網絡連接。
防火墻技術
(1)防火墻的概念
在計算機領域,把一種能使一個網絡及其資源不受網絡"墻"外"火災"影響的設備稱為"防火墻"。用更專業一點的話來講,防火墻(FireW all)就是一個或一組網絡設備(計算機系統或路由器等),用來在兩個或多個網絡間加強訪問控制,其目的是保護一個網絡不受來自另一個網絡的攻擊。可以這樣理解,相當于在網絡周圍挖了一條護城河,在唯一的橋上設立了安全哨所,進出的行人都要接受安全檢查。
防火墻的組成可以這樣表示:防火墻=過濾器+安全策略(+網關)。
(2)防火墻的實現方式
①在邊界路由器上實現;
②在一臺雙端口主機(dual-homed host)上實現;
③在公共子網(該子網的作用相當于一臺雙端口主機)上實現,在此子網上可建立含有停火區結構的防火墻。
(3)防火墻的網絡結構
網絡的拓撲結構和防火墻的合理配置與防火墻系統的性能密切相關,防火墻一般采用如下幾種結構。
①最簡單的防火墻結構
這種網絡結構能夠達到使受保護的網絡只能看到"橋頭堡主機"( 進出通信必經之主機), 同時,橋頭堡主機不轉發任何TCP/IP通信包, 網絡中的所有服務都必須有橋頭堡主機的相應代理服務程序來支持。但它把整個網絡的安全性能全部托付于其中的單個安全單元,而單個網絡安全單元又是攻擊者首選的攻擊對象,防火墻一旦破壞,橋頭堡主機就變成了一臺沒有尋徑功能的路由器,系統的安全性不可靠。
②單網端防火墻結構
其中屏蔽路由器的作用在于保護堡壘主機(應用網關或代理服務) 的安全而建立起一道屏障。在這種結構中可將堡壘主機看作是信息服務器,它是內部網絡對外發布信息的數據中心,但這種網絡拓撲結構仍把網絡的安全性大部分托付給屏蔽路由器。系統的安全性仍不十分可靠。
③增強型單網段防火墻的結構
為增強網段防火墻安全性,在內部網與子網之間增設一臺屏蔽路由器,這樣整個子網與內外部網絡的聯系就各受控于一個工作在網絡級的路由器,內部網絡與外部網絡仍不能直接聯系,只能通過相應的路由器與堡壘主機通信。
④含"停火區"的防火墻結構
針對某些安全性特殊需要, 可建立如下的防火墻網絡結構。 網絡的整個安全特性分擔到多個安全單元, 在外停火區的子網上可聯接公共信息服務器,作為內外網絡進行信息交換的場所。
網絡反病毒技術
由于在網絡環境下,計算機病毒具有不可估量的威脅性和破壞力, 因此計算機病毒的防范也是網絡安全性建設中重要的一環。網絡反病毒技術也得到了相應的發展。
網絡反病毒技術包括預防病毒、檢測病毒和消毒等3種技術。(1) 預防病毒技術,它通過自身常駐系統內存,優先獲得系統的控制權,監視和判斷系統中是否有病毒存在,進而阻止計算機病毒進入計算機系統和對系統進行破壞。這類技術是:加密可執行程序、引導區保護、系統監控與讀寫控制(如防病毒卡)等。(2)檢測病毒技術,它是通過對計算機病毒的特征來進行判斷的技術,如自身校驗、關鍵字、文件長度的變化等。(3)消毒技術,它通過對計算機病毒的分析,開發出具有刪除病毒程序并恢復原文件的軟件。
網絡反病毒技術的實施對象包括文件型病毒、引導型病毒和網絡病毒。
網絡反病毒技術的具體實現方法包括對網絡服務器中的文件進行頻繁地掃描和監測;在工作站上采用防病毒芯片和對網絡目錄及文件設置訪問權限等。
隨著網上應用不斷發展,網絡技術不斷應用,網絡不安全因素將會不斷產生,但互為依存的,網絡安全技術也會迅速的發展,新的安全技術將會層出不窮,最終Internet網上的安全問題將不會阻擋我們前進的步伐
同步在網絡游戲中是非常重要的,它保證了每個玩家在屏幕上看到的東西大體是一樣的。其實呢,解決同步問題的最簡單的方法就是把每個玩家的動作都向其他玩家廣播一遍,這里其實就存在兩個問題:1,向哪些玩家廣播,廣播哪些消息。2,如果網絡延遲怎么辦。事實上呢,第一個問題是個非常簡單的問題,不過之所以我提出這個問題來,是提醒大家在設計自己的消息結構的時候,需要把這個因素考慮進去。而對于第二個問題,則是一個挺麻煩的問題,大家可以來看這么個例子:
比如有一個玩家A向服務器發了條指令,說我現在在P1點,要去P2點。指令發出的時間是T0,服務器收到指令的時間是T1,然后向周圍的玩家廣播這條消息,消息的內容是“玩家A從P1到P2”有一個在A附近的玩家B,收到服務器的這則廣播的消息的時間是T2,然后開始在客戶端上畫圖,A從P1到P2點。這個時候就存在一個不同步的問題,玩家A和玩家B的屏幕上顯示的畫面相差了T2-T1的時間。這個時候怎么辦呢?
有個解決方案,我給它取名叫 預測拉扯,雖然有些怪異了點,不過基本上大家也能從字面上來理解它的意思。要解決這個問題,首先要定義一個值叫:預測誤差。然后需要在服務器端每個玩家連接的類里面加一項屬性,叫TimeModified,然后在玩家登陸的時候,對客戶端的時間和服務器的時間進行比較,得出來的差值保存在TimeModified里面。還是上面的那個例子,服務器廣播消息的時候,就根據要廣播對象的TimeModified,計算出一個客戶端的CurrentTime,然后在消息頭里面包含這個CurrentTime,然后再進行廣播。并且同時在玩家A的客戶端本地建立一個隊列,保存該條消息,只到獲得服務器驗證就從未被驗證的消息隊列里面將該消息刪除,如果驗證失敗,則會被拉扯回P1點。然后當玩家B收到了服務器發過來的消息“玩家A從P1到P2”這個時候就檢查消息里面服務器發出的時間和本地時間做比較,如果大于定義的預測誤差,就算出在T2這個時間,玩家A的屏幕上走到的地點P3,然后把玩家B屏幕上的玩家A直接拉扯到P3,再繼續走下去,這樣就能保證同步。更進一步,為了保證客戶端運行起來更加smooth,我并不推薦直接把玩家拉扯過去,而是算出P3偏后的一點P4,然后用(P4-P1)/T(P4-P3)來算出一個很快的速度S,然后讓玩家A用速度S快速移動到P4,這樣的處理方法是比較合理的,這種解決方案的原形在國際上被稱為(Full plesiochronous),當然,該原形被我篡改了很多來適應網絡游戲的同步,所以而變成所謂的:預測拉扯。
另外一個解決方案,我給它取名叫 驗證同步,聽名字也知道,大體的意思就是每條指令在經過服務器驗證通過了以后再執行動作。具體的思路如下:首先也需要在每個玩家連接類型里面定義一個TimeModified,然后在客戶端響應玩家鼠標行走的同時,客戶端并不會先行走動,而是發一條走路的指令給服務器,然后等待服務器的驗證。服務器接受到這條消息以后,進行邏輯層的驗證,然后計算出需要廣播的范圍,包括玩家A在內,根據各個客戶端不同的TimeModified生成不同的消息頭,開始廣播,這個時候這個玩家的走路信息就是完全同步的了。這個方法的優點是能保證各個客戶端之間絕對的同步,缺點是當網絡延遲比較大的時候,玩家的客戶端的行為會變得比較不流暢,給玩家帶來很不爽的感覺。該種解決方案的原形在國際上被稱為(Hierarchical master-slave synchronization),80年代以后被廣泛應用于網絡的各個領域。
最后一種解決方案是一種理想化的解決方案,在國際上被稱為Mutual synchronization,是一種對未來網絡的前景的良好預測出來的解決方案。這里之所以要提這個方案,并不是說我們已經完全的實現了這種方案,而只是在網絡游戲領域的某些方面應用到這種方案的某些思想。我對該種方案取名為:半服務器同步。大體的設計思路如下:
首先客戶端需要在登陸世界的時候建立很多張廣播列表,這些列表在客戶端后臺和服務器要進行不及時同步,之所以要建立多張列表,是因為要廣播的類型是不止一種的,比如說有local message,有remote message,還有global message 等等,這些列表都需要在客戶端登陸的時候根據服務器發過來的消息建立好。在建立列表的同時,還需要獲得每個列表中廣播對象的TimeModified,并且要維護一張完整的用戶狀態列表在后臺,也是不及時的和服務器進行同步,根據本地的用戶狀態表,可以做到一部分決策由客戶端自己來決定,當客戶端發送這部分決策的時候,則直接將最終決策發送到各個廣播列表里面的客戶端,并對其時間進行校對,保證每個客戶端在收到的消息的時間是和根據本地時間進行校對過的。那么再采用預測拉扯中提到過的計算提前量,提高速度行走過去的方法,將會使同步變得非常的smooth。該方案的優點是不通過服務器,客戶端自己之間進行同步,大大的降低了由于網絡延遲而帶來的誤差,并且由于大部分決策都可以由客戶端來做,也大大的降低了服務器的資源。由此帶來的弊端就是由于消息和決策權都放在客戶端本地,所以給外掛提供了很大的可乘之機。
綜合以上三種關于網絡同步派系的優缺點,綜合出一套關于網絡游戲傳輸同步的較完整的解決方案,我稱它為綜合同步法(colligate synchronization)。大體設計思路如下:
首先將服務器需要同步的所有消息從劃分一個優先等級,然后按照3/4的比例劃分出重要消息和非重要消息,對于非重要消息,把決策權放在客戶端,在客戶端邏輯上建立相關的決策機構和各種消息緩存區,以及相關的消息緩存區管理機構.
對于非重要消息,客戶端的大體處理流程,其中有一個客戶端被動行為值得大家注意,其中包括對服務器發過來的某些驗證代碼做返回,來確保消息緩存中的消息和服務器端是一致的,從而有效的防止外掛來篡改本地消息緩存。其中的消息來源是包括本地的客戶端響應玩家的消息以及遠程服務器傳遞過來的消息。
對于重要消息,比如說戰斗或者是某些牽扯到玩家一些比較敏感數據的操作,則采用另外一套方案,該方案首先需要在服務器和客戶端之間建立一套Ping System,然后服務器保存和用戶的及時的ping值,當ping比較小的時候,響應玩家消息的同時先不進行動作,而是先把該消息反饋給服務器,并且阻塞,服務器收到該消息,進行邏輯驗證之后向所有該詳細廣播的有效對象進行廣播(包括消息發起者),然后客戶端收到該消息的驗證,才開始執行動作。而當ping比較大的時候,客戶端響應玩家消息的同時立刻進行動作,并且同時把該消息反饋給服務器,值得注意的是這個時候還需要在本地建立一個無驗證消息的隊列,把該消息入隊,執行動作的同時等待服務器的驗證,還需要保存當前狀態。服務器收到客戶端的請求后,進行邏輯驗證,并把消息反饋到各個客戶端,帶上各個客戶端校對過的本地時間。如果驗證通過不過,則通知消息發起者,該消息驗證失敗,然后客戶端自動把已經在進行中的動作取消,恢復原來狀態。如果驗證通過,則廣播到的各個客戶端根據從服務器獲得校對時間進行對其進行拉扯,保證在該行為完成之前完成同步。
至此,一個比較成熟的網絡游戲的同步機制已經初步建立起來了,接下來的邏輯代碼就根據各自不同的游戲風格以及側重點來寫了。
同步是網絡游戲最重要的問題,如何同步也牽扯到各個方面的問題,比如說游戲的規模,游戲的類型以及各種各樣的方面,對于規模比較大的游戲,在同步方面可以下很多的工夫,把消息分得十分的細膩,對于不同的消息采用不同的同步機制,而對于規模比較小的游戲,則可以采用大體上一樣的同步機制,究竟怎么樣同步,沒有個定式,是需要根據自己的不同情況來做出不同的同步決策的
網游同步算法之導航推測(Dead Reckoning)算法:
在了解該算法前,我們先來談談該算法的一些背景資料。大家都知道,在網絡傳輸的時候,延遲現象是很普遍的,而在基于Server/Client結構下的網絡游戲的同步也就成了很頭疼的問題,在保證客戶端響應用戶本地指令流暢的情況下,沒法有效的保證的同步的及時性。同樣,在軍方也有類似的事情發生,即使是同一LAN里面的機器,也會因為傳輸的延遲,導致一些運算的失誤,介于此,美國國防部投入了大量的資金用于研究一種比較的好的方案來解決分布式系統中的延遲問題,特別是一個叫分布式模擬運動(Distributed Interactive Simulation)的系統,這套系統呢,其中就提出了一套號稱是Latency Hiding & Bandwidth Reduction的方案,命名為Dead Reckoning。呵呵,來頭很大吧,恩,那么我們下面就來看看這套系統的一些觀點,以及我們如何把它運用到我們的網絡游戲的同步中。
本文使用的表示方法和VC6的Memory視圖一致,即:左上表示低位。
提示2:下文提到的“類大小”嚴格上來說是該類經過實例化的對象的大小。當然了,光研究長度的話,兩者差別不大,因為:CClassA objA,sizeof(CClassA)和sizeof(objA)得到的結果都是一樣的。
一、真空類
class CNull
{
};
長度:1
內存結構:
??
評注:長度其實為0,這個字節作為內容沒有意義,可能每次都不一樣。
二、空類
class CNull2
{
public:
CNull2(){printf("Construct\n");}
~CNull2(){printf("Desctruct\n");}
void Foo(){printf("Foo\n");}
};
長度:1
內存結構:
??
評注:同真空類差不多,內部的成員函數并不會影響類大小。
三、簡單類
class COneMember
{
public:
COneMember(int iValue = 0){m_iOne = iValue;};
private:
int m_iOne;
};
長度:4
內存結構:
00 00 00 00 //m_iOne
評注:成員數據才影響類大小。
四、簡單繼承
class CTwoMember:public COneMember
{
private:
int m_iTwo;
};
長度:8
內存結構:
00 00 00 00 //m_iOne
CC CC CC CC //m_iTwo
評注:子類成員接在父類成員之后。
五、再繼承
class CThreemember:public CTwoMember
{
public:
CThreemember(int iValue=10) {m_iThree = iValue;};
private:
int m_iThree;
};
長度:12
內存結構:
00 00 00 00 //m_iOne
CC CC CC CC //m_iTwo
0A 00 00 00 //m_iThree
評注:孫類成員接在子類之后,再再繼承就依此類推了。
六、多重繼承
class ClassA
{
public:
ClassA(int iValue=1){m_iA = iValue;};
private:
int m_iA;
};
class ClassB
{
public:
ClassB(int iValue=2){m_iB = iValue;};
private:
int m_iB;
};
class ClassC
{
public:
ClassC(int iValue=3){m_iC = iValue;};
private:
int m_iC;
};
class CComplex :public ClassA, public ClassB, public ClassC
{
public:
CComplex(int iValue=4){m_iComplex = iValue;};
private:
int m_iComplex;
};
長度:16
內存結構:
01 00 00 00 //A
02 00 00 00 //B
03 00 00 00 //C
04 00 00 00 //Complex
評注:也是父類成員先出現在前邊,我想這都足夠好理解。
七、復雜一些的繼承
不寫代碼了,怕讀者看了眼花,改畫圖。

長度:32
內存結構:
01 00 00 00 //A
02 00 00 00 //B
03 00 00 00 //C
04 00 00 00 //Complex
00 00 00 00 //OneMember
CC CC CC CC //TwoMember
0A 00 00 00 //ThreeMember
05 00 00 00 //VeryComplex
評注:還是把自己的成員放在最后。
只要沒涉及到“虛”(Virtual),我想沒什么難點,不巧的是“虛”正是我們要研究的內容。
八、趁熱打鐵,看“虛繼承”
class CTwoMember:virtual public COneMember
{
private:
int m_iTwo;
};
長度:12
內存結構:
E8 2F 42 00 //指針,指向一個關于偏移量的數組,且稱之虛基類偏移量表指針
CC CC CC CC // m_iTwo
00 00 00 00 // m_iOne(虛基類數據成員)
評注:virtual讓長度增加了4,其實是多了一個指針,關于這個指針,確實有些復雜,別的文章有具體分析,這里就不岔開具體講了,可認為它指向一個關于虛基類偏移量的數組,偏移量是關于虛基類數據成員的偏移量。
九、“閉合”虛繼承,看看效果

長度:24
內存結構:
14 30 42 00 //ClassB的虛基類偏移量表指針
02 00 00 00 //m_iB
C4 2F 42 00 //ClassC的虛基類偏移量表指針
03 00 00 00 //m_iC
04 00 00 00 //m_iComplex
01 00 00 00 //m_iA
評注:和預料中的一樣,虛基類的成員m_iA只出現了一次,而且是在最后邊。當然了,更復雜的情況要比這個難分析得多,但虛繼承不是我們研究的重點,我們只需要知道:虛繼承利用一個“虛基類偏移量表指針”來使得虛基類即使被重復繼承也只會出現一次。
十、看一下關于static成員
class CStaticNull
{
public:
CStaticNull(){printf("Construct\n");}
~CStaticNull(){printf("Desctruct\n");}
static void Foo(){printf("Foo\n");}
static int m_iValue;
};
長度:1
內存結構:(同CNull2)
評注:可見static成員不會占用類的大小,static成員的存在區域為靜態區,可認為它們是“全局”的,只是不提供全局的訪問而已,這跟C的static其實沒什么區別。
十一、帶一個虛函數的空類
class CVirtualNull
{
public:
CVirtualNull(){printf("Construct\n");}
~CVirtualNull(){printf("Desctruct\n");}
virtual void Foo(){printf("Foo\n");}
};
長度:4
內存結構:
00 31 42 00 //指向虛函數表的指針(虛函數表后面簡稱“虛表”)
00423100:(虛表)
41 10 40 00 //指向虛函數Foo的指針
00401041:
E9 78 02 00 00 E9 C3 03 … //函數Foo的內容(看不懂)
評注:帶虛函數的類長度就增加了4,這個4其實就是個指針,指向虛函數表的指針,上面這個例子中虛表只有一個函數指針,值就是“0x00401041”,指向的這個地址就是函數的入口了。
十二、繼承帶虛函數的類
class CVirtualDerived : public CVirtualNull
{
public:
CVirtualDerived(){m_iVD=0xFF;};
~CVirtualDerived(){};
private:
int m_iVD;
};
長度:8
內存結構:
3C 50 42 00 //虛表指針
FF 00 00 00 //m_iVD
0042503C:(虛表)
23 10 40 00 //指向虛函數Foo的指針,如果這時候創建一個CVirtualNull對象,會發現它的虛表的內容跟這個一樣
評注:由于父類帶了虛函數,子類就算沒有顯式聲明虛函數,虛表還是存在的,虛表存放的位置跟父類不同,但內容是同的,也就是對父類虛表的復制。
十三、子類有新的虛函數
class CVirtualDerived: public CVirtualNull
{
public:
CVirtualDerived(){m_iVD=0xFF;};
~CVirtualDerived(){};
virtual void Foo2(){printf("Foo2\n");};
private:
int m_iVD;
};
長度:8
內存結構:
24 61 42 00 //虛表指針
FF 00 00 00 //m_iVD
00426124:(虛表)
23 10 40 00
50 10 40 00
評注:虛表還是只有一張,不會因為增加了新的虛函數而多出另一張來,新的虛函數的指針將添加在復制了的虛表的后面。
十四、當純虛函數(pure function)出現時
class CPureVirtual
{
virtual void Foo() = 0;
};
class CDerivePV : public CPureVirtual
{
void Foo(){printf("vd: Foo\n");};
};
長度:4(CPureVirtual),4(CDerivePV)
內存結構:
CPureVirtual:
(不可實例化)
CDerivePV:
28 50 42 00 //虛表指針
00425028:(虛表)
5A 10 40 00 //指向Foo的函數指針
評注:帶純虛函數的類不可實例化,因此列不出其“內存結構”,由其派生類實現純虛函數。我們可以看到CDerivePV雖然沒有virtual聲明,但由于其父類帶virtual,所以還是繼承了虛表,如果CDerivePV有子類,還是這個道理。
十五、虛函數類的多重繼承
前面提到:(子類的虛表)不會因為增加了新的虛函數而多出另一張來,但如果有多重繼承的話情況就不是這樣了。下例中你將看到兩張虛表。

大小:24
內存結構
F8 50 42 00 //虛表指針
01 00 00 00 //m_iA
02 00 00 00 //m_iB
E8 50 42 00 //虛表指針
03 00 00 00 //m_iC
04 00 00 00 //m_iComplex
004250F8:(虛表)
5A 10 40 00 //FooA
55 10 40 00 //FooB
64 10 40 00 //FooComplex
004250E8:(虛表)
5F 10 40 00 //FooC
評注:子類的虛函數接在第一個基類的虛函數表的后面,所以B接在A后面,Complex接在B后面。基類依次出現,子類成員接在最后面,所以m_iComplex位于最后面。