2012年12月31日 星期一

到底是誰殺了PDI??

到底是誰殺了PDI??
每當程控資料一有異常時, 系統工程員就要準備接受挑戰, 因為現場人員幾乎會先認定是系統問題, 當你查明確定是由HMI以人工作業方式觸發後, 現場人員又會挑釁你, 到底是哪台電腦動了殺機, 這下頭大了, 只能像福爾摩斯一樣, 逐一查明各個HMI的log, 找出所使用的殺人兇器  (重點是還會被疑犯案過程是造假的...)

圖一. 犯案流程
 
舉例: 在現有架構下,  當HMI下達刪除鋼捲PDI後 (如上圖一)
(1) 以~xxxxx^xxxxx^xxxxx^xxxxx&的訊息格式, 通知server (此時記錄著pc name)
(2) pccomm解譯上述(1)訊息格式後, 通知distribution (此時記錄著pc name)
(3) distribution依據訊息代碼, 決定分派至schmgr (此時記錄著pc name)
(4) schmgr確認該鋼捲PDI沒有被排程鎖定後, 通知pdimgr (此時抹滅掉pc name)
(5) pdimgr被通知殺了該鋼捲PDI (此時已無pc name)
(6) pdimgr將該犯案事件寫入資料庫, 但是誰殺的?? 演變成一齣懸案
 

原來是現有架構成了幫兇, 各個後端程式皆是以MSMQ進行作業流程的觸發點, pccomm & distribution是既定的公用程式, 故HMI的pc name皆得以保留記錄 (可以規範記錄兇手)

但schmgr & pdimgr仍由系統人員開發, 除非佛心來著, 每每要將HMI的pc name, 額外記錄於要通知其它程式的MSMQ, 若有一個疏忽, 毀了屍滅了跡, 就讓兇手逍遙法外了

現有架構各個程式的作業模式如下:
recieve from MSMQ >> do some activity >> send to MSMQ or not
當接收一個message後, 進行某些活動, 再傳送另一個message給其它程式
試想, 當傳送一個message時, 從已接收的message中, 將pc name擷取出來, 記錄於其中, 應該就可行囉!
 
最後, 哪到底是誰殺了PDI?? 請愛用ApMsgHandler::getClntName (), 因為兇手就藏在細節裡

changeset#4

SockTcpHandler 增加 setCheckAckSize(), 由開發人員自訂是否需要檢查ack message size

因應某一伺服端程控,  ack message 屬於不定長度, 現有SockTcpHandler無法判定, 導致視為異常作業, 自動終止連線, 再重新連線

目前SockTcpHandler::setAckSize (), 意即指定元件須確認 ack message size相同時, 才會將ack messsage以call back方式引入, 反之, 視為異常作業, 自行斷線重新連線

試以增加一函式, SockTcpHandler::setCheckAckSize (), 由同仁自訂是否需要由元件檢查ack message size, 預設為true (檢查)
反之(不檢查), 元件則直接將接收的 ack message以call back方式引入


提供使用範例程式碼如下::

void NGOSndCtrl::run()
{
    int rcv_len = 0;
    myTcpClnt.openClient(server, _TCPPORT);
    myTcpClnt.setHeaderSize(sizeof(TCP_HEAD));
    myTcpClnt.setAckSize(sizeof(TCP_ACK), sizeof(TCP_HEAD)+1);
    myTcpClnt.setNeedAck(1);
    myTcpClnt.setCheckAckSize (ChkACKSize_NO);  // 設定為不檢查
    myTcpClnt.setTimeout(1000); // -1 is no time out
    /*an option, custom to ckeck reply ack message */
    myTcpClnt.setOnReadAck(CB(onReadAck));
    myTcpClnt.runClient();

    .....(以下省略)
}

當要接收不同的ack message時, 應如何善用SockTcpHandler::setAckSize()

同位提出一個需求討論:
假若不同的L2 client side (L2A, L2B), 需回覆不同格式的ack format時(ACK_A, ACK_B), 如何因應??
struct ackA {
  TCP_HEAD head;
  char blank1;
  char acept; /**< accept or not */
  char trans_id[4];
  char err_code[4];
  char blank2[2];
  char err_msg[31];
} ACK_A;

struct ackB {
  TCP_HEAD head;
  char blank1;
  char acept; /**< accept or not */
  char trans_id[4];
  char err_code[4];
} ACK_B;

建議做法::
可以於SockTcpHandler::setAckSize (ack_size, accept_position)進行調整
    ack_size: 客戶端連線所回覆的ack message資料長度
    accept_position: 客戶端連線對於前一筆送出資料的驗證結果的字元位置


針對L2A : myTcpSvr.setAckSize(sizeof(ACK_A), sizeof(TCP_HEAD)+2);
針對L2B : myTcpSvr.setAckSize(sizeof(ACK_B), sizeof(TCP_HEAD)+2);


提供一般Control Class::run ()的範例程式碼::

void NGORcvCtrl::run()
{
    /* specify the header size of message */
    myTcpSvr.setHeaderSize(sizeof(TCP_HEAD));
    /* specify the ack size and function position */
    myTcpSvr.setAckSize(sizeof(TCP_ACK), sizeof(TCP_HEAD)+1);
    /* set need to reply ack */
    myTcpSvr.setNeedAck(1);
    /* assign a funcion, when tcp server on receive complete message will call back */
    myTcpSvr.setOnReceive(CB(onReceive));
    /* assign a funcion, when tcp server on reply custom ack will call back it */
    myTcpSvr.setOnReply(CB_RP(onReply));
    /* run the tcp server */
    myTcpSvr.runSingleSvr ();
}