1. 程式人生 > >KY-RTI分佈模擬技術:第四章 C++程式設計

KY-RTI分佈模擬技術:第四章 C++程式設計

       本章講述如何在Linux作業系統上設計GNU C++程式。演示了2個程式:聊天程式chat和時間管理程式time。chat使用HLA的互動類進行通訊,沒有采用tick服務;time使用HLA的物件類進行通訊,採用tick服務;並說明了如何簡單修改就可以變成一個不採用tick服務的程式。

4.1 多執行緒設計模式

       RTI由Central RTI Component(CRC)和Local RTI Component(LRC)組成。在KY-RTI中,可以把CRC簡單地理解為RTI伺服器,LRC理解為libRTI-NG庫。

       基於KY-RTI的C++模擬程式可以僅包含2個.cpp檔案,如下圖所示。一個負責請求RTI;另一個負責從RTI接收回調。Main.cpp通過呼叫HLA服務把請求傳給LRC,再由LRC傳給CRC;CRC將回調傳給LRC,再由LRC傳給HwFederateAmbassador.cpp。HwFederateAmbassador.cpp是DMSO RTI習慣採用的檔名,專門用來接收RTI的回撥資料。在KY-RTI的各類程式設計語言中,接收回調的檔名都採用了HwFederateAmbassador這個專有名稱。

                                                              圖4.1 模擬成員採用多執行緒技術實現

       在KY-RTI程式設計模式中,Main.cpp和HwFederateAmbassador.cpp分別位於兩個獨立的執行緒中;當然,如果在同一個執行緒中,既要向RTI傳送請求,又要從RTI接收請求,容易造成死鎖。如果兩個執行緒採用全域性變數來共享資料,則同時讀寫一個共享變數時會引起衝突,因此應加鎖或採用臨界區來進行互斥訪問。

4.2 多語言間的資料傳輸

       以C++和Java語言為例,兩種程式相互之間傳輸資料時涉及到主機位元組序與網路位元組序兩個概念。

網路位元組序:不同計算機體系結構的儲存機制可能不同,通訊雙方交流的資訊單元(位元、位元組、字、雙字等)應該按照統一的規則進行傳送和接收才行。如果不達成一致的理解, 通訊雙方將無法進行正確的通訊。網路位元組序規定採用big-endian規則,通訊雙方需要把資料按照big-endian編碼之後再通過網路傳輸。

       主機位元組序就是CPU的位元組序。x86主機的位元組序為little-endian。因此,在x86主機上傳輸資料的時候要把資料從little-endian轉換為big-endian編碼後,再通過網路傳送出去。

       下面舉一個例子說明big-endian與little-endian的區別:

       int size  = 0x01020304;

       size的型別為int,int有4個位元組,其中01為最高位的位元組,04為最低位的位元組。那麼在記憶體(或檔案)中,該值的儲存順序為:

      記憶體(或檔案)地址:0x00000001(高位)  0x00000002  0x00000003  0x00000004(低位)  

      big-endian         :           01                              02                 03                   04

      little-endian       :           04                              03                 02                   01

       如圖4.2所示,如果兩個程式都是C++程式,則它們在通訊時不進行編碼轉換也能正常通訊。

                                                              圖4.2 C++程式之間不進行網路編碼能正常通訊

       Java虛擬機器採用big-endian,而執行在Java虛擬機器中的Java程式的主機位元組序也為big-endian。如圖4.3所示,如果C++程式與Java程式進行通訊,不進行編碼轉換就不能正確通訊。

                                                              圖4.3 C++程式與Java程式之間不進行網路編碼則通訊異常

       為簡化使用者程式設計邏輯,基於KY-RTI設計的兩個模擬程式可以不進行網路編碼就能正常通訊,但有1個簡單要求。如圖4.4所示,如果C++把123.45這個浮點數變成字串'1'、'2'、'3'、'.'、'4'、'5',則C++不需要編碼,Java程式也能夠正確地接收。

                                                              圖4.4 C++程式與Java程式在KY-RTI中的通訊方式

       下面幾行程式碼是從4.4節時間管理程式中摘來的幾條語句,略有改動,使用者可能會經常用到。

       (1)如果兩個模擬成員都是C++程式,則使用下面語句對整型屬性xPos進行打包和解包。

             打包:

                       int xPos;

                       pAttrs->add(g_hxPos, (char*)&xPos, sizeof(int));

             解包:

                       int xPos;

                       theAttributes.getValue(i, (char*)&xPos, valueLength);

        (2)如果兩個模擬成員採用不同語言程式設計,則應將整型屬性xPos轉為字串。

                 也就是說,傳送方應使用字串傳送,接收方應將接收到的字串轉為整數。

              打包:

                       char s[20];

                       sprintf(s, "%d\0", xPos);

                       pAttrs->add(g_hxPos, (char*)s, strlen(s));

              解包:

                       int xPos;

                       char str[20];

                       theAttributes.getValue(i, (char*)str, valueLength);

                       xPos = atoi(str);

4.3 聊天程式

4.3.1需求分析

       本專案需要實現一個類似微信群或者QQ群聊天功能的GNU C++程式,每個人傳送的訊息都能夠被群裡的其他人看到。

4.3.2專案設計

       每條聊天資訊應包含2個內容:聊天者暱稱、聊天的一句話,這樣接收者就會知道是誰在發言。“聊天者暱稱”用name表示,“聊天的一句話”用sentence表示,兩個都是字串型別。因為HLA是面向物件的,傳送的資料要麼採用物件類,要麼採用互動類。本專案可採用互動類,將name和sentence封裝到一個名叫“chat”的互動類中,如下列程式碼所示。

class chat {           //互動類

       int  name;     //引數

       int  sentence; //引數

}

       下面採用KY-OMT建立fed檔案,相應的chat.fed檔案已經在3.3.3中建立完成,將該檔案儲存到KY-RTI的bin目錄。

       本專案對時間沒有特別要求,不需要採用HLA時間管理機制。當RTI收到聊天資訊時就立即傳送給其他人,不需要呼叫tick服務。

4.3.3程式碼設計

       該程式比較簡單,一個Chat.cpp和HwFederateAmbassador.cpp就可以實現。前者通過呼叫建立聯邦執行、加入聯邦執行、公佈和訂購互動類、傳送互動,模擬完成時退出聯邦;後者用來接收RTI的回撥訊息。

       Chat.cpp程式碼說明:

8-10行:定義互動類及其引數控制代碼變數;

27-36行:建立聯邦執行;

38-53行:加入聯邦執行;

57-59行:獲取互動類及其引數控制代碼;

62行:公佈互動類,只有公佈之後才能夠向RTI傳送互動;

64行:訂購互動類,只有訂購之後才能夠從RTI收到其他人的聊天內容;

68-87行:迴圈操作,每次輸入一句話,並呼叫sendInteraction服務傳送給RTI;當用戶輸入“exit”時則退出執行;

89-94行:退出聯邦執行,不再參加模擬;

96-107行:銷燬聯邦。如果是最後一個模擬成員執行該操作,則整個模擬結束。

                                                              表4.1  C++聊天示例:Chat.cpp

  1. #include "HwFederateAmbassador.hh"
  2. #include <RTI.hh>
  3. #include <fedtime.hh>
  4. #include <iostream>
  5. using namespace std;
  6. RTI::InteractionClassHandle     hChatClass;
  7. RTI::ParameterHandle        hChatName;
  8. RTI::ParameterHandle        hChatSentence;
  9. int hw_main(int argc, char *argv[])
  10. {
  11.     const char *federationExecutionName = "chat";
  12.     const char *FEDfile = "chat.fed";
  13.     char federateName[50];
  14.     cout << "Please input your name: ";
  15.     cin >> federateName;
  16.     try {
  17.         RTI::RTIambassador       rti;
  18.         HwFederateAmbassador     fedAmb;
  19.         RTI::FederateHandle      federateId;
  20.         try {
  21.             rti.createFederationExecution(federationExecutionName, FEDfile);
  22.         }
  23.         catch ( RTI::FederationExecutionAlreadyExists& e ) {
  24.             //According to the HLA standard, only the first federate can call this service succesfully.
  25.             //cerr << "FED_HW: Note: Federation execution already exists." << e << endl;
  26.         } catch ( RTI::Exception& e ) {
  27.             cerr << "FED_HW: ERROR:" << e << endl;
  28.             return -1;
  29.         }
  30.         try {
  31.             federateId = rti.joinFederationExecution(federateName, federationExecutionName, &fedAmb);
  32.         } catch (RTI::FederateAlreadyExecutionMember& e) {
  33.             cerr << "FED_HW: ERROR: " << argv[1]
  34.                  << " already exists in the Federation Execution "
  35.                  << federationExecutionName << "." << endl;
  36.             cerr << e << endl;
  37.             return -1;
  38.         } catch (RTI::FederationExecutionDoesNotExist&) {
  39.             cerr << "FED_HW: ERROR: Federation Execution "
  40.                  << "does not exists."<< endl;
  41.             return -1;
  42.         } catch ( RTI::Exception& e ) {
  43.             cerr << "FED_HW: ERROR:" << e << endl;
  44.             return -1;
  45.         }
  46.         /////////////////////////////////////////////////////////////////////////
  47.         hChatClass = rti.getInteractionClassHandle("chat");
  48.         hChatName = rti.getParameterHandle("name", hChatClass);
  49.         hChatSentence = rti.getParameterHandle("sentence", hChatClass);
  50.         //如果向外傳送,則需要公佈
  51.         rti.publishInteractionClass(hChatClass);
  52.         //如果需要接收,則必須訂購
  53.         rti.subscribeInteractionClass(hChatClass);
  54.         string szSentence;
  55.         cin.ignore();
  56.         while (0 != strcmp(szSentence.c_str(), "exit")) {
  57.             cout << "Please input a sentence: ";
  58.             getline(cin, szSentence);
  59.             RTI::ParameterHandleValuePairSet* pParams = NULL;
  60.             long numParams(2);
  61.             pParams = RTI::ParameterSetFactory::create (numParams);
  62.             pParams->add(hChatName,(char*)federateName, strlen(federateName)+1);
  63.             pParams->add(hChatSentence,(char*)szSentence.c_str(), szSentence.size());
  64.             try {
  65.                 rti.sendInteraction(hChatClass, *pParams, "");
  66.             } catch(...) {
  67.                 cerr << "Error: send interaction" << endl;
  68.             }
  69.             pParams->empty();
  70.             delete pParams;   // Deallocate the memory
  71.         }
  72.         try {
  73.             rti.resignFederationExecution( RTI::DELETE_OBJECTS_AND_RELEASE_ATTRIBUTES );
  74.         } catch ( RTI::Exception& e ) {
  75.             cerr << "FED_HW: ERROR:" << e << endl;
  76.             return -1;
  77.         }
  78.         try {
  79.             rti.destroyFederationExecution( federationExecutionName );
  80.         } catch ( RTI::FederatesCurrentlyJoined& /* e */ ) {
  81.             cerr << "FED_HW: FederatesCurrentlyJoined" << endl;
  82.             return 0;
  83.         } catch ( RTI::FederationExecutionDoesNotExist& /* e */) {
  84.             cerr << "FED_HW: FederationExecutionDoesNotExist" << endl;
  85.             return 0;
  86.         } catch ( RTI::Exception& e ) {
  87.             cerr << "FED_HW: ERROR:" << e << endl;
  88.             return -1;
  89.         }
  90.     } catch (RTI::ConcurrentAccessAttempted& e) {
  91.         cerr << e << endl;
  92.         return -1;
  93.     } catch ( RTI::Exception& e ) {
  94.         cerr << "FED_HW: ERROR:" << e << endl;
  95.         return -1;
  96.     }
  97.     return 0;
  98. }
  99. int
  100. main(int argc, char** argv)
  101. {
  102.     return hw_main(argc, argv);
  103. }

       HwFederateAmbassador.cpp程式碼說明:

10-24行:由於不處理時間引數,因此如果接收到這種型別的receiveInteraction互動,則直接呼叫不帶時間引數的服務來統一處理;

26-63行:處理接收到的聊天資訊並輸出。

                                                              表4.2  C++聊天示例:HwFederateAmbassador.cpp

  1. #include "fedtime.hh"
  2. #include "HwFederateAmbassador.hh"
  3. #include <iostream>
  4. using namespace std;
  5. extern RTI::InteractionClassHandle  hChatClass;
  6. extern RTI::ParameterHandle     hChatName;
  7. extern RTI::ParameterHandle     hChatSentence;
  8. void HwFederateAmbassador::receiveInteraction (
  9.     RTI::InteractionClassHandle       theInteraction,         // supplied C1
  10.     const RTI::ParameterHandleValuePairSet& theParameters,  // supplied C4
  11.     const RTI::FedTime&                     theTime,             // supplied C4
  12.     const char                                *theTag,             // supplied C4
  13.     RTI::EventRetractionHandle             theHandle)          // supplied C1
  14. throw (
  15.     RTI::InteractionClassNotKnown,
  16.     RTI::InteractionParameterNotKnown,
  17.     RTI::InvalidFederationTime,
  18.     RTI::FederateInternalError)
  19. {
  20.     //call the next service.
  21.     this->receiveInteraction( theInteraction, theParameters, theTag );
  22. }
  23. void HwFederateAmbassador::receiveInteraction (
  24.     RTI::InteractionClassHandle       theInteraction, // supplied C1
  25.     const RTI::ParameterHandleValuePairSet& theParameters,  // supplied C4
  26.     const char                             *theTag)         // supplied C4
  27. throw (
  28.     RTI::InteractionClassNotKnown,
  29.     RTI::InteractionParameterNotKnown,
  30.     RTI::FederateInternalError)
  31. {
  32.     RTI::ParameterHandle paraHandle;
  33.     RTI::ULong           valueLength;
  34.     //Usage of char[] and string
  35.     char name[256];   //name of sender
  36.     string sentence;  //sentence of sender
  37.     for ( int i = 0; i < theParameters.size(); i++ ) {
  38.         paraHandle = theParameters.getHandle( i );
  39.         if(paraHandle == hChatName) {
  40.             theParameters.getValue(i, (char*)name, valueLength);
  41.             /*If name is a double number, you can do this way.
  42.                 double name;
  43.                 theParameters.getValue(i, (char*)&name, valueLength);
  44.             */
  45.         } else if(paraHandle == hChatSentence) {
  46.             sentence.resize(theParameters.getValueLength(i));
  47.             theParameters.getValue(i, (char*)sentence.c_str(), valueLength);
  48.         } else {
  49.             cout << "Receive wrong parameter handle." << endl;
  50.         }
  51.     }
  52.     cout << endl << name << ": " << sentence << endl;
  53. }

4.3.4編譯執行

編譯GNU C++程式,通常要先建一個Makefile檔案。在Makefile檔案中指定了所使用的編譯器、編譯命令、編譯選項、連線的庫和庫目錄等。

       表4.3是編譯聊天程式的Makefile檔案,對於其他程式而言,格式都一樣。在該Makefile中,只要關心第10、11、13行即可。其中,第10、11行指明瞭本程式有兩個.cpp檔案;第13行則指明瞭最終生成的可執行程式名。

                                                              表4.3  Makefile

  1. C++FLAGS = -DRTI_USES_STD_FSTREAM -DREENTRANT -D_RH72_GCC302 \
  2.                 -DRTI_HAS_THREADS -DPOSIX_PTHREAD_SEMANTICS -g -O3
  3. LDFLAGS += -g -O3
  4. LIBS = -lRTI-NG -lfedtime -lpthread
  5. C++ = g++
  6. OBJS =  Chat.o \
  7.         HwFederateAmbassador.o
  8. EXECUTABLE = chat
  9. RTI_ROOT_DIR = ${RTI_HOME}/${RTI_BUILD_TYPE}
  10. RTI_INC_DIR = ${RTI_ROOT_DIR}/include
  11. RTI_LIB_DIR = ${RTI_ROOT_DIR}/lib
  12. INC_PATH = -I${RTI_INC_DIR} -I.
  13. LIB_PATH = -L${RTI_LIB_DIR}
  14. # Build targets
  15. %.o : %.cpp
  16.     @echo
  17.     @echo Compiling $< ...
  18.     @echo
  19.     ${C++} -c ${C++FLAGS} ${INC_PATH} $< -o [email protected]
  20. default: ${EXECUTABLE}
  21. ${EXECUTABLE}: ${OBJS}
  22.     @echo
  23.     @echo Linking [email protected] ...
  24.     @echo
  25.     ${C++} ${LDFLAGS} ${OBJS} -o [email protected] ${LIB_PATH} ${LIBS}
  26. clean:
  27.     rm -rf *.o core *~ .depend Templates.DB ${EXECUTABLE}

       執行下列命令生成可執行程式chat。

       make

       測試專案:在銀河麒麟作業系統上執行2個GNU C++模擬成員,測試KY-RTI通訊功能。

       測試步驟:

       第1步:修改RTI.rid,關閉tick開關。

       因為本程式沒有使用tick服務,所以需要關閉tick開關。

       檢視當前目錄下是否存在RTI.rid,若沒有則執行chat,則會自動生成該檔案。將RTI.rid檔案中的“;; UsingTickSwitch On”改為“;; UsingTickSwitch Off”。

       第2步:啟動KY-RTI。注意,KY-RTI的IP地址和埠號要與RTI.rid一致。

       第3步:如圖4.5和圖4.6所示,開啟兩個終端,執行2個模擬成員,開始模擬。執行命令:

       ./chat

       測試結果表明:聊天功能正常,KY-RTI支援中英文傳輸。

                                                              圖4.5 GNU C++聊天者1

                                                              圖4.6 GNU C++聊天者2

4.4 時間管理程式

4.4.1需求分析

       本模擬專案的名稱為“TimeManageme