1、class MsgQueue
1.1 接收資料
- 透過 msgrcv() 函式將指定的訊息佇列的資料讀取到資料緩衝 DataBuffer 中;
- 將 DataBuffer 指向的資料解析成訊息結構體指標(MsgStruct *),;
- 取出訊息結構體的訊息物件(message).
std::int32_t length = msgrcv(m_msg_queue_id, reinterpret_cast<void *>(dataBuffer.pos()), MAX_BUFFER_LENGTH, m_process_id, IPC_NOWAIT);
MsgStruct *msgStructPtr = reinterpret_cast<MsgStruct *>(dataBuffer.pos());
std::string str(msgStructPtr->message, msgStructPtr->message_length);
1.2 傳送資料
- 將字串型別的引數轉成資料緩衝 DataBuffer 型別;
- 呼叫 msgsnd() 將資料緩衝傳送到訊息佇列;
2、class message
訊息型別共 9 種,分為請求測試用例、傳送訊息、開啟、停止、結束測試、測試用例結果、故障、其他、永久開啟等。
2.1 class MessageBase
常用的 3 種訊息型別:1)傳送訊息型別 SendMessage;2)啟動測試用例型別 start;3)停止測試用例型別 stop。
- 記錄訊息所掛載的 epoll;
- 獲取型別函式
- 響應函式:記錄源地址、目標地址、請求 id、包型別
子類過載的響應函式邏輯是呼叫基類的響應函式後,將傳入的 json 物件轉成測試用例物件,並存於容器,將容器傳給處理函式後,將測試用例轉成響應資訊的 message 欄位。
2.2 class MessageQueryTestItem 請求測試用例型別
在繼承基礎上,新增 toMessage()。
- 1)toMessage():用於將測試用例物件轉成 json 格式,並序列化到 jsonObject:
jsonMessage.push_back(TestItemConfig::testItem2Json(it));
nlohmann::json jsonObject;
jsonObject["message_type"] = getType();
jsonObject["message"] = jsonMessage;
- 2)響應函式:從測試用例配置檔案讀取測試用例物件,並將測試用例物件轉成 Json 型別,作為響應的欄位值。
2.3 class MessageSendMsg
在繼承基礎上,新增處理函式。
- 1) 處理函式 deal():具體做法是將測試用例物件傳給所掛載的 epoll,由 epoll 將測試用例物件所要接
收的 msg 傳送給測試用例例項。
2.4 class MessageStart
在繼承基礎上,新增處理函式 deal() 和開啟函式 startAll()/startSpecify()。
-
1)處理函式具體操作是賦值測試用例的 result 欄位;
-
2)開啟函式是將測試用例的 json 描述資訊轉成測試用例物件,並置放在容器裡;
2.5 class MessageStop
-
在繼承基礎上,新增停止函式。
-
1)停止函式:將測試用例的 json 描述資訊轉成測試用例物件,並將其放入容器。將容器傳給所掛載的 epoll,並由 epoll 停止測試用例(stopTestItem());
3、class MessageManager 訊息管理器
-
1)訊息型別物件容器:訊息管理器維護一個雜湊對映表,按名字儲存每種訊息型別物件。
-
2)響應函式:解析請求報文,判斷訊息型別,呼叫相應的響應函式。
4、class Package 傳送給 PC 的資料包文的打包與解包
4.1 PC 與下位機資料互動的通訊過程如下:
- PC --> 下位機:請求啟動的測試用例 json 資訊({"message": testItemJson, "message_type":"send_message"});
- 下位機 --> PC:若下位機啟動測試用例成功,傳送響應報文,訊息內容在轉述請求包的 message 欄位基礎上,
新增處理結果 result 欄位("result": "ok"); - 下位機 --> PC:若接收到測試用例返回的結果({"name":"xxx", "value":"xxx"}),傳送響應報文,訊息內容在
轉述請求包的 message 欄位基礎上,新增處理結果 result 欄位("result": {"name":"xxx", "value":"xxx"})
日誌例項:
2024-03-05 14:23:10.600 [info] 給pc傳送報文:
{"message":
{ "bin":"test_soc_soft_ver","chip":"SOC1","key":11,"name":"start_soc_soft_ver","parameter":"eol",
**"result":"{\"name\":\"rtc\",\"value\":[\"MPQ79700FS's RTC Read Test starts:\"]}"**,
"test":true,"type":"parallel_test"},
"message_type":"test_item_result"
}
4.2 打包基操作流程
打包基操作分為 2 個階段。階段 1 私有打包方法將文字型別轉為資料包 PackageData 型別:將字串整合成資料包的欄位(源頭src、去向des、包id、包型別、訊息內容);階段 2 公有打包方法將 PackageData 型別轉為 DataBuffer 型別:申請一段字串空間,將 PackageData 的欄位逐個填入,返回時將字串轉成 DataBuffer 型別。
// 階段 1:文字型別->PackageData
packageDataPtr->src = m_local_chip_name;
packageDataPtr->des = ADDRESS_PC;
packageDataPtr->id = generateMessageId();
packageDataPtr->package_type = PackageType::Report;
packageDataPtr->message = jsonObject.dump();
// 階段 2:
std::memcpy(bufPtr, packageDataPtr->src.c_str(), packageDataPtr->src.size());
bufPtr += SRC_LENGTH;
xxx
return std::make_shared<DataBuffer>(buf);
4.3 打包封裝操作
- 下位機與 PC 的互動資料包型別有 4 種,分別為心跳包、請求包、響應包、報告包等。打包封裝操作均是將文字/物件型別打包成 PackageData 型別,分別有 4 種封裝操作:心跳包打包、故障請求打包、結束測試打包、測試用例結果打包。
4.4 解包操作
按結構體逐部分解析字串。
class Channel
將 fd 封裝成 Channel,對於 TCP fd,封裝成 TcpChannel;對於 pipefd,封裝成 TestItemChannel。
5.1 ChannelBase 通道基
含有讀 fd、讀緩衝、讀操作、寫 fd、寫緩衝佇列、寫操作、epoll。
5.1.1 放置待寫資料操作
將資料封裝成資料結構體,新增進寫緩衝佇列,並讓 epoll 監聽通道基的寫 fd 的寫事件 m_epollEx_ptr->notifyWrite(shared_from_this());
。
5.1.2 讀操作
從讀 fd 讀取資料到讀緩衝 read(m_read_fd, m_receive_buf_ptr->pos(), m_receive_buf_ptr->readOnceSize())
。
5.1.3 寫操作
從寫緩衝佇列彈出資料緩衝,將資料緩衝的資料寫入寫 fd,write(m_write_fd, dataBufferPtr->pos(), dataBufferPtr->writeOnceSize())
。
5.2 TcpChannel
- 含有名稱描述符
m_name
,其值為 client 的名字(SOC2/PC); - 指向測試用例物件的指標、指向訊息佇列的指標、指向測試用例的指標
m_file_ptr
5.3 TestItemChannel
- 含有名稱描述符
m_name
,其值為測試用例的名稱; - 測試用例通道是隻提供讀 fd(其值為 popen 返回的檔案指標),epoll 只能讀取測試用例輸出到標準輸出裝置的資料。
- Question: 測試用例如何透過訊息佇列接收 epoller 傳來的 cmd 資料?
備註: poepn() 呼叫 fork() 產生子程序,建立管道連到子程序的標準輸出裝置或標準輸入裝置,然後返回一個檔案指標。隨後程序便可利用此檔案指標來讀取子程序的輸出裝置或寫入到子程序的標準輸入裝置中。
5.4 ChannelFactory
5.4.1 建立測試用例通道
透過 popen() 啟動就緒佇列裡的測試用例,並用通道對返回的檔案指標 fp(作為讀 fd)。
5.4.2 建立 TCP 通道
將 fd 作為 readfd 與 writefd
6、測試用例管理器
測試用例佇列、掛載的 epoll、設定測試用例、清除測試用例、開啟下一測試用例。
6.1 設定就緒測試用例
將需要執行的測試用例容器逐個彈出,新增進就緒測試用例緩衝佇列 m_test_item_queue;讓 epoll 監聽 m_notify_test_item_fd 的可寫事件 notifyNextTestItem EpollUtil::epollCtrl(m_epfd, EPOLL_CTL_MOD, m_notify_test_item_fd, EPOLLOUT | EPOLLET);
備註: 思考 m_notify_test_item_fd 在哪裡接收寫
6.2 清除就緒測試用例
將就緒測試用例佇列 m_test_item_queue 清空。
6.3 啟動測試用例
透過 popen() 啟動就緒佇列裡的測試用例,並用通道對返回的檔案指標 fp(作為讀 fd) 進行封裝。將讀 fd 與 通道作為鍵值對儲存在 epoll 上。
7、上行執行緒
- 上行執行緒都要掛靠在 Epoll,Epoll 記錄上行 fd,若上行 fd 不存在,則建立上行 TCP 通道。
8、class epoller
8.1 初始化
-
建立訊息管理器、測試用例管理器、上行執行緒、epollfd 例項(epollfd 指示該例項)。
-
對於部署在 SOC2(isSlave) 的 epoller 既承擔 TestItem 的 server 角色,又承擔 SOC1 epoller 的 client 角色。它需要接收來自 SOC1 的轉發請求。所以需要建立上行 TCP 監聽通道(m_uplevel_fd = 連線描述符 connFd)。
參考連結:https://blog.csdn.net/dearQiHao/article/details/102877786 # socket程式設計之 connect()函式
// connect() 連線請求後,伺服器端只是把連線請求資訊記錄到等待佇列,只有 accept 操作後,才能進行資料交換(read/write)
m_uplevel_fd = EpollUtil::connectTo(ip, port); // m_uplevel_fd = 連線描述符 connFd
- 對於部署在 SOC1(isMaster) 的 epoller 只承擔 PC 端和 SOC2 epoller 的server 角色。它建立監聽描述符以監聽 PC 和 SOC2 的訊息。
// 建立監聽 fd,等待可讀事件
m_listenfd = EpollUtil::createListenFd(m_epfd, m_config_ptr->getListenPort());
->
// 把監聽套接字加入 epoll
epollCtrl(epfd, EPOLL_CTL_ADD, listenfd, EPOLLIN | EPOLLET);
8.2 執行
1)訊息管理器對 StartForever 請求報文進行響應;
2)建立 epoll_event
結構體(記錄 fd 就緒時,核心儲存和返回的資料);
3)等待 epoll 例項(epollfd)所掛載的 fd 所監聽的事件發生,並將就緒事件儲存在 epoll_event
結構體;
4)處理事件:
- 若就緒事件是新連線到來事件(監聽描述符
m_listenfd
可讀事件),則接受 TCP 連線; - 若就緒事件是通知執行測試用例(通知描述符
m_notify_test_item_fd
可讀事件),則啟動測試用例; - 若就緒事件是其他可讀可寫事件,則執行處理讀寫操作。
8.3 傳送資料包
- 透過資料包記錄的目標物件名稱,找到對應通道;
- 將引數資料包封裝成資料緩衝物件,再將資料緩衝物件封裝成資料結構體({srcAddr,dataBuffer});