2011年5月6日
#
錯誤碼很重要,可以由此判斷網絡連接到底發生了神馬事情,從而驅動高層邏輯的行為。只有籠統的錯誤碼判斷的網絡層是不夠規范的,鄙人覺得有些錯誤碼還是需要在網絡層就區分開的,特此記錄一些當前實驗的錯誤碼以及發生原因。
以下是一部分在async_receive()的handler處捕獲到的比較有用的錯誤碼
錯誤碼(十進制) |
枚舉 |
發現原因 |
10009 |
boost::asio::error::bad_descriptor |
在一個已經關閉了的套接字上執行async_receive() |
995 |
boost::asio::error::operation_aborted |
正在async_receive()異步任務等待時,本端關閉套接字 |
10054 |
boost::asio::error::connection_reset |
正在async_receive()異步任務等待時,遠端的TCP協議層發送RESET終止鏈接,暴力關閉套接字。常常發生于遠端進程強制關閉時,操作系統釋放套接字資源。 |
2 |
boost::asio::error::eof |
正在async_receive()異步任務等待時,遠端關閉套接字,這里跟10054發生的情況似乎一樣,但是實際上應該是有區別的,具體神馬區別,由回復中jack的說法,這個是遠端正常關閉套接字。 |
只是一些淺陋的測試,目前覺得有用的也就是這幾個,不正確的地方請送我雞蛋。
2011年1月30日
#
前面必須使用到的,類似下面的指令
F:\sdk\boost>bjam --link=static --threading=multi --runtime-link=shared debug release stage
后面需要選擇編譯器和要編譯的庫
--toolset=msvc-9.0 --with-date_time --with-thread......
2010年12月29日
#
這兩個東西在一起問題真呀么多......前些日子才寫的一個注意事項的隨筆,這回又有問題需要記錄,好吧,開新文寫。
問題:由于項目的復雜度,問題的表現與原因其實相差十萬八千里。
原因:MFC在打開和保持文件后(即打開CFileDialog對話框后),就會修改進程的當前目錄,就是SetCurrentDirectoy(),導致OGRE里那些用相對路徑做Location的資源目錄下的文件全部無法讀取(其實我覺得OGRE應該把這些相對目錄在讀取文件的時候換成絕對目錄)。具體就是openResource()中調用stat()出錯,文件系統中找不到指定文件。
解決方法:在合適的地方調用SetCurrentDirectoy()把進程當前目錄設置回進程工作目錄吧......
于是又是一下午+半個晚上的調試時間......
2010年12月23日
#
接上文
《備忘隨筆系列1:MFC與OGRE聯姻注意事項》之后,再記錄一下內存錯誤,經過無數次莫名其妙的內存問題之后,發現一些找不著北的內存Crash問題出現的原因都很荒謬,所以本篇主要例舉一下近期出現的一些怪異內存問題和讓人啼笑皆非的原因所在。
問題1:編譯器在編譯那些訪問成員變量的代碼時算錯了相對于this指針的偏移字節數;賦值給下面一個變量時,卻修改了上面一個變量的值。
原因:與我共事的某位大仙由于酷愛使用結構體傳遞網絡包,所以在某頭文件里用#pragma pack(1)包括住了整個頭文件,一不小心把#include "其他頭文件"那些行也給包括了進去,其中不乏Windows.h stl云云......
解決辦法:當然那個啥......把#pragma pack(1)的位置往下去幾行,還是細心點吧...浪費了整整一天調試。
問題2:從網絡另一端機器發過來一個結構體,分別接收一個結構體中的多個數據成員和一次性接收整個結構體取出的數據不同。
原因:這是個很2的情形,兩個相同的結構體分別在不同的頭文件中,且一個有#pragma pack(1),一個沒有。
解決辦法:如果要用結構體傳遞網絡包,還是共用頭文件吧......
其實......很多內存問題很不好描述,我也不經常出現如上那樣糾結的問題,所以下面我還是說一個最常見的內存問題(0x.....地址訪問沖突)和原因吧:
“0x.....地址訪問沖突”這個Crash基本上每個人都遇到,而且經常遇到,但是大部分都很容易解決。判斷問題的原因可以看這幾點:
原因1:如果0x....這個值很小,一般就比0大一些,而且是在訪問某對象中的數據成員時出錯的,那么這基本都是因為該對象指針為空,你用了空對象指針調用了代碼。
原因2:如果0x...值同樣很小,但是并非在訪問某對象中的數據成員時出錯,而是調用某函數那一行時出錯的,那么這個函數十有八九是個虛函數,如果我說中的話,那原因應該如前面的原因1相同,只是這回是讀取虛函數表時就崩了。
原因3:如果0x...值類似是0xcdcdcdcd和0xeeeccc或者與這相近的數,且同樣是在訪問數據成員或調用虛函數的時候出的問題,那么這就算是個野指針問題了,釋放了就別再用啊。
原因4:內存越界,這個對程序造成的麻煩比任何麻煩都要大,但是問題并不隱蔽,記得為每個類的數據成員進行必要的初始化。
原因5:使用了memset或ZeroMemory清空一些對象或對象數組。特別是對象數組,很容易讓人忽略這個問題。有些程序員會覺得某對象里都是可以這樣清空的數據成員,所以便這樣做了,但是往往虛函數表指針會被忽略,這個指針絕對不能一起被清空的。
總結:不要讓表達索引的整形在初始化后是個未知值;不要讓指針沒有在初始化時被賦0值;不要不檢查指針的值就拿它訪問成員函數和成員數據;不要重復釋放指針所指對象;不要使用釋放后和未初始化的內存數據;可以的話使用智能指針;釋放指針所指地址后,為指針賦0值;只有在完全是內部類型構成且沒有多態的類型對象上使用memset為對象賦值。
細節決定那啥來著,一些細節雖然不是什么難事,但是一旦卡住總是會很煩心,需要太多時間去調試,耽誤的是寶貴的項目進度,所以我將在這里把一些總結貼出來,愿能給國內的游戲技術圈同僚們一點小幫助,節約寶貴的時間,畢竟總是在網絡上攝取營養,算是回報社會吧。
本文記錄最近發現的一些 MFC 和 OGRE1.7.2版本 聯姻的注意事項:
問題1:創建Ogre的CView窗口后,無法截獲鼠標點擊和移動信息,只能獲取鼠標滾輪信息。
原因及解決方案:傳遞CView窗口句柄時,請一定使用externedWindowHandle的屬性key,切記不要使用parentWindowHandle,因為parentWindowHandle是讓CView成為渲染窗口的父窗口,鼠標鍵盤消息都不會路由到CView上,而是在渲染窗口里被截獲;而externedWindowHandle是讓CView窗口本身成為渲染窗口,所以CView才能正常截獲到輸入消息。
問題2:當解決問題1之后,發現使用externedWindowHandle繪制出的窗口很小,而使用parentWindowHandle時則正常
原因及解決方案:注意繼承CView::OnSize()函數響應WM_SIZE消息,但請切記:千萬別在OnSize中調用Ogre::RenderWindow::resize()函數,這會導致OnSize()函數的遞歸回調,因為Ogre::RenderWindow::resize()函數中會調用AdjustWindow()和SetWindowPos()函數,這會導致發送WM_SIZE消息并縮小窗口,從而導致問題的發生。
問題3:如何解決窗口重置大小的問題
解決方案:在OnSize()中
不能調用Ogre::RenderWindow::resize()函數,而
應該調用Ogre::RenderWindow::windowMovedOrResized()函數,通知RenderWindow在渲染前重新設置Viewport的寬高比例。
問題4:怎樣確保主渲染循環
分析:上網看了一些相關的解決方案,發現大多使用WM_TIMER消息來維持OGRE的主渲染循環,這應該是下下策的方案了吧......當然還有其他的實現方案,譬如開另一個線程,這個方法還是可行的,但是總有些不對味,因為渲染明明應該在主線程中才是最佳方案。于是我就看了一下MFC閑下來的時候都干了些什么,最后發現了以下解決方案,應該算是很不錯但并不難的解決辦法了,為什么沒見網上有人提供這樣的方案讓我很不理解,窩著藏著也得不到半點好處:
解決方案:使用空閑回調。該回調是需要繼承CWinApp::OnIdle()函數(好像是叫這個,反正肯定帶Idle這個單詞),當主線程中的消息循環沒有取到消息時(調用PeekMessage()沒有獲取到消息),就會去調用這個函數,于是......就在這個函數里調用繪制一幀吧:Ogre::RenderWindow::update(),另外有動畫的話還需要調用Ogre::Root::_fireFrameRenderingQueued(),因為動畫更新在這里。如果是想讓所有渲染對象都更新一幀的話,直接調用Ogre::Root::renderOneFrame()吧。
解決方案不一定最好,也不一定適合你的情況,但愿能盡微薄之力,也是作為我個人的備忘吧。
2010年11月20日
#
最近在給公司里碼一個場景編輯器,大致得實現的功能有:
地形高度刷
地形紋理刷
放置小物件和房屋
放置粒子系統
設置路徑點和只能攝像機點
算是個簡單的不能再簡單的場景編輯器了吧...但是這樣的一個工具還是很頭痛的,特別是用C++來寫...
頭痛的原因不是別的,正是這個表現層和后臺數據同步問題。這個在C++的UI庫中目前還真沒有什么現成的好辦法,于是開始造輪子,為MFC寫了PropertySet和OperatorStack。
首先這個UI數據和內存數據雙向同步的問題直接讓我崩潰了...由于以前寫過一些工具,知道這東西如果不做個設計就開始沖著功能寫的話會有什么后果。嗯,于是繼承封裝了CMFCPropertyGridCtrl控件,為每個葉子屬性項封裝了一個LeafItem,根據屬性名來更新PropertySet里對應的數據......具體實現幾千字略- - 最終成型時代碼這樣:
DynamicObject obj;
propertyGrid.attachObject(obj);
這里的DynamicObject繼承PropertySet,于是propertyGrid控件就會顯示obj里所有的屬性數據了...然后是雙向更新問題,目前是給Property里加了一個eventValueChanged事件響應,讓PropertyGridCtrl監聽這些數據的變化,而propertyGridCtrl這個UI上的數據變化同樣是派生實現CMFCPropertyGridCtrl的值變化響應函數來給綁定的LeafItem更新數據,也是直接就刷新到Property里了。
還有OperatorStack.....這個是操作棧,記錄用戶操作的,用于撤銷和重做的操作,也用到了PropertySet來記錄變化對象的屬性快照,嗯,叫SnapShootRecord的類里面記錄的都是一個對象的變化屬性。
先就記錄這么多,很亂很不容易懂,主要給我自己做個記錄的,沒啥貢獻,實際上還有很多不好用的地方,所以最近在想一些改進設計,等我想好了放上來詳細設計和源碼吧.......
2010年10月12日
#
實現的目的是為了在一些特定情況下不去使用boost的filter_streambuf,不使用boost::iostreams的理由如下:
1、基于運行時配置的過濾器,效率稍低
2、對于網絡通訊而言,boost的filter_streambuf乃至整個iostreams庫都顯得較為臃腫。
所以,我自己編寫了一套filter_streambuf,繼承了std::streambuf,并配合自己重新設計的archive和batch_data進行網絡通訊,無論是效率還是易用性上都超出使用boost的iostreams。而boost的那套東西經過我的反復使用后,覺得更適合用在文件讀寫和數據持久化上。
如果要說哪里不如boost的filter_stream,也就是boost的filter_streambuf可以動態配置filter,而我使用的是模板技術將filter的關系在編譯期就關聯了起來,所以只能是靜態配置filter。下面是具體使用時的完整例子代碼:
1 #include <ccs/util/ios/ifilter_streambuf.hpp>
2 #include <ccs/util/ios/ofilter_streambuf.hpp>
3 #include <ccs/util/ios/memory_terminal.hpp>
4
5 using namespace ccs;
6 using namespace util;
7
8 // 輸出過濾
9 struct my_ofilter
10 {
11 typedef ios::ofilter_tag tag_type;
12
13 template<typename OutT>
14 std::streamsize write(const char* p, std::streamsize n, OutT& _out)
15 {
16 std::streamsize i = 0;
17 for (; i < n; ++i)
18 {
19 char c = p[i];
20 if (_out.write(&++c, 1) != 1)
21 break;
22 }
23 return i;
24 }
25 };
26
27 // 輸入過濾
28 struct my_ifilter
29 {
30 typedef ios::ifilter_tag tag_type;
31
32 template<typename InT>
33 std::streamsize read(char* p, std::streamsize n, InT& _in)
34 {
35 std::streamsize i = 0;
36 for (; i < n; ++i)
37 {
38 char c;
39 if (_in.read(&c, 1) != 1)
40 break;
41 p[i] = --c;
42 }
43 return i;
44 }
45 };
46
47 // 輸出內存設備
48 struct memory_odevice
49 {
50 typedef ios::dest_tag tag_type;
51
52 std::streamsize write(const char* p, std::streamsize n, ios::memory_oterminal& _out)
53 {
54 return _out.write(p, n);
55 }
56 };
57
58 // 輸入內存設備
59 struct memory_idevice
60 {
61 typedef ios::source_tag tag_type;
62
63 std::streamsize read(char* p, std::streamsize n, ios::memory_iterminal& _in)
64 {
65 return _in.read(p, n);
66 }
67 };
68
69
70 int main(int _Argc, char** _Args)
71 {
72 char buf[256];
73 ios::memory_oterminal memout(buf, 256);
74 ios::memory_iterminal memin(buf, 256);
75 ios::ifilter_streambuf<ios::memory_iterminal, mpl::list2<my_ifilter, memory_idevice> > insbuf(&memin);
76 ios::ofilter_streambuf<ios::memory_oterminal, mpl::list2<my_ofilter, memory_odevice> > outsbuf(&memout);
77 std::istream is(&insbuf);
78 std::ostream os(&outsbuf);
79
80 int num = 188;
81 os.write((char*)&num, sizeof(int));
82 os.flush();
83 is.read((char*)&num, sizeof(int));
84
85 std::cout << num << std::endl;
86 system("pause");
87 }
代碼中的意思就是將寫入的數據逐字節的加1,并保存在內存緩沖里,然后又從內存緩沖中讀出,逐字節減1,并輸出到控制臺,一套經過過濾的讀寫流便完成了。由于使用了模板元的list作為鏈接,在release模式下所有的過濾器操作都是內聯的,這雖然也是我預想的效果,但看完匯編碼之后,著實讓我高興了一晚上,這種成就感真的是programer最大的樂趣。
需要說明的是:代碼中的mpl::list2是自己實現的模板元鏈表...過段時間考慮研究一下boost的并替換過來,因為那個list后面的2讓我覺得很不夠智能...當然,如果boost的list實現過于復雜,或是不能讓我的代碼完全內聯化的話,肯定不會考慮使用。
完成這個之后,我便準備著手構建cge項目,所謂的cge,就是cloud game engine的縮寫...顧名思義就是使用了云技術的游戲引擎,我想在業余時間嘗試一些顛覆傳統cs架構的在線游戲引擎架構設計,具體難點估計會有2個:
1、運用gpgpu group的并行運算技術,考慮使用目前市場占用率最大的nvidia tesla服務器配合cuda,在服務器用physX實現一定的物理模擬。
2、在即時性較強的在線游戲中,ping值一直是最大的挑戰,所以有選擇性的使用云計算技術,這是架構設計上的挑戰。
關于cge的設計思考和規劃,會另外開貼具體闡述,并記錄開發進度和情況。
記錄一下這個,容易忘記的DXSDK_DIR環境變量,可以在cmake里添加dx的sdk路徑,否則找死也找不到rendersystem_direct3d的項目文件。
2010年7月1日
#
今天記錄一下長久以來屢次犯的錯,每次都是換一種方法編碼來繞過這個問題實現功能的,因為這個問題太過隱蔽,導致今天才發現其中真正的原因...下面進行問題描述:
1
std::map<std::string, Value> keyValue; // 在函數內部分配的堆棧對象(局部變量)
2
ReadData(keyValue);// 從dll中導出的函數
3
keyValue.clear(); // delete中出現assert異常
第一行是在應用程序中的堆棧中分配的內存空間。
第二行是我自己寫的dll庫,用來讀取一些數據加入到keyValue中。
第三行是清空keyValue,其實如果不寫這一行的話,keyValue也會在函數結尾時清空,到那時同樣會出現錯誤。
這一切乍一看沒啥問題,keyValue是局部變量,為什么局部變量的釋放會出現異常錯誤呢?這是因為第二行ReadData的緣故。ReadData的邏輯在另外一個可執行模塊中,在其中分配的內存空間不一定與當前模塊在同一個堆區。
我們知道,std::map是一個樹結構的容器,我在ReadData內部往keyValue中添加了數據,keyValue中會在堆區中分配樹節點,而這個節點將會在當前模塊在keyValue的析構中被釋放。也就是說,我無意中在dll模塊中分配了堆空間,又無意中在exe模塊中企圖釋放該空間,這樣的行為導致錯誤是不足為怪的。
時刻牢記,在一個模塊中分配和釋放同一塊內存區域,警惕你所看不見的內存分配和釋放。
2010年4月24日
#
好久沒寫blog了,這次初步完成了一個文檔化的網絡流框架,這玩意兒是咱自己這樣叫,但具體是啥玩意兒呢?其實就是將網絡通訊數據結構給串行化到緩沖里,再發送到網絡的另一端,由另一端再串行化到相應的類型對象里。恩,這聽起來沒啥難度呀,但事實并非如此,呵呵,該架構建立在asio基礎之上,目前完成了tcp通訊部分,基本可以很方便的使用了。
為啥我要寫這么個架子,因為網絡通訊需要考慮很多情況,如粘包、未接收完整、緩沖不夠大等情況,而且在項目開發過程中,不斷的添加和修改一些通信協議相關的數據包結構。為了讓程序員不要管那么多麻煩的情況,同時易于修改和添加新的通訊協議,于是就寫了這么個架構,不過今天比較忙,還是下次傳上用例代碼吧,源碼可能會在不久以后發布的通用庫模板庫里找到。
恩,咱要發布自己的一個開源庫,建立在stl和boost基礎上,可跨平臺編譯 0 0......
到時候再說了。