試實戰(zhàn):用ModScan32和Wireshark拆解報文定位問題)
搞Modbus協(xié)議開發(fā)的朋友應(yīng)該都有過這種經(jīng)歷明明從站設(shè)備就在那兒上位機(jī)軟件也能連上但讀寫寄存器就是不對——數(shù)據(jù)讀出來全0、寫參數(shù)設(shè)備沒反應(yīng)、偶發(fā)超時、真機(jī)上調(diào)得好好的程序到了現(xiàn)場亂跳。這時候光靠ModScan32去看“結(jié)果”很難定位到底是在哪一層出了問題。把Wireshark拉進(jìn)來把報文從鏈路層到應(yīng)用層全部拆開看才能找到真正的根因。這篇是系列的第二篇上一篇已經(jīng)把ModScan32的基本使用和Wireshark的基礎(chǔ)過濾講過了這次直接進(jìn)入正題用ModScan32做操作、Wireshark做旁路抓包把Modbus TCP的每一幀報文從十六進(jìn)制到業(yè)務(wù)字段全部讀懂再把調(diào)試中常見的數(shù)據(jù)錯亂、地址偏移、功能碼不匹配等問題逐一拆透。1. 工作臺上的組合為什么是 ModScan32 加 Wireshark1.1 ModScan32 到底解決什么問題ModScan32在Modbus開發(fā)里的地位有點像是萬用表在電路維修里的地位。它本身是個主站模擬工具能主動發(fā)起Modbus請求往從站設(shè)備或模擬器上讀寫線圈、離散輸入、保持寄存器、輸入寄存器。你不需要寫一行代碼直接在界面上指定IP、端口、功能碼、起始地址、數(shù)據(jù)長度它就能把請求發(fā)出去并且把返回的數(shù)據(jù)按你指定的格式顯示出來。但它的能力邊界也在這它能告訴你“讀到了什么”幾乎不告訴你“讀的過程發(fā)生了什么”。舉個例子一臺儀表的保持寄存器地址是40001到40010你用ModScan32填起始地址1去讀結(jié)果數(shù)據(jù)全是0。你換了起始地址0數(shù)據(jù)就出來了。這時候ModScan32顯示得很正常甚至你都沒覺得有問題。但如果現(xiàn)場同事問一句“為什么地址1讀不到”你就得從協(xié)議的角度解釋清楚這一步恰恰需要看報文。1.2 Wireshark 在 Modbus 調(diào)試?yán)锏慕巧玏ireshark解決的是“過程可見”的問題。它能把每個TCP幀的完整字節(jié)抓下來然后根據(jù)Modbus協(xié)議規(guī)范幫你解析出MBAP頭、功能碼、寄存器地址、數(shù)據(jù)值甚至把異常響應(yīng)里的異常碼都翻譯成人話。調(diào)試的時候我一般是讓ModScan32保持輪詢Wireshark在旁邊一直抓包等數(shù)據(jù)不對時直接停下來看最后一個請求和對應(yīng)的響應(yīng)幾秒鐘就能定位問題在哪一端。尤其遇到設(shè)備廠商的從站固件行為比較特殊的情況只有Wireshark能告訴你真相。比如某個從站對不支持的地址返回了“非法數(shù)據(jù)地址”異常碼02但ModScan32界面上可能只會顯示一個問號或者干脆空白你不會知道是請求本身發(fā)錯了還是從站拒絕了你。而在Wireshark里異常的響應(yīng)幀一眼就能看到功能碼變成了0x83后面跟著02問題邊界立刻清晰。1.3 這套組合適合哪些場景我自己的經(jīng)驗里這幾個場景用這套組合最值回票價第一新接入一款不熟悉的從站設(shè)備要快速確認(rèn)寄存器映射、字節(jié)序、支持的功能碼第二寫上位機(jī)程序之前用ModScan32和模擬從站先把協(xié)議鏈路驗證一遍排除設(shè)備端問題第三程序?qū)懲曷?lián)調(diào)時遇到超時、地址對不上、數(shù)據(jù)錯誤用Wireshark抓包看請求是否按預(yù)期組裝第四排查非本機(jī)通信的場景比如中間經(jīng)過路由器、網(wǎng)關(guān)或者串口服務(wù)器只要數(shù)據(jù)沒按預(yù)期跑通抓包就能判斷數(shù)據(jù)是在哪一段被改壞或丟棄的。這套組合能覆蓋從協(xié)議驗證、設(shè)備摸底到程序聯(lián)調(diào)的大部分環(huán)節(jié)。后面所有章節(jié)都會圍繞這個組合展開咱們先把手上的工具準(zhǔn)備齊。2. 環(huán)境準(zhǔn)備與會話搭建2.1 常用軟件選型和模擬從站準(zhǔn)備做Modbus TCP調(diào)試環(huán)境里至少要有三樣?xùn)|西主站工具M(jìn)odScan32、抓包工具Wireshark、一個能響應(yīng)Modbus請求的從站。從站可以是真實設(shè)備但開發(fā)階段我更推薦先用手頭的模擬從站軟件把鏈路打通再拿到真機(jī)上驗證。模擬從站的好處是可控——寄存器值隨便改支持的功能碼隨便配甚至能故意制造異常響應(yīng)來測試主站的健壯性。市面上常見的從站模擬工具有不少Modbus Slave這類工具用起來比較直觀能配置多個寄存器區(qū)還能模擬異常。如果只是臨時起一個簡單的從站一些開源的工具也能跑。選型的標(biāo)準(zhǔn)是能看到收到什么請求、能手動修改寄存器值、能配置Unit ID。別小看這三點實際排查問題時你會發(fā)現(xiàn)“從站到底有沒有收到請求”和“從站回了什么內(nèi)容”比什么都重要。Wireshark的安裝有一點要特別提醒抓回環(huán)流量時需要Npcap支持。如果你裝Wireshark時用的是老版本W(wǎng)inPcap可能在回環(huán)網(wǎng)卡上抓不到包建議把抓包組件更新到Npcap版本并且在安裝時勾選“Support loopback traffic”。這一步?jīng)]做好后面ModScan32和模擬從站跑在同一臺電腦上時你會什么都抓不到白折騰半天。2.2 通過 ModScan32 快速建立與從站的通信啟動ModScan32之后首要任務(wù)是建立連接。在界面的連接區(qū)域選擇Modbus TCP/IP類型Server地址填從站的IP如果從站就在本機(jī)就填127.0.0.1端口默認(rèn)502。如果你的從站配置了多個Unit ID要在Device ID欄里填對否則請求會被從站直接忽略或返回異常。連接建立后ModScan32默認(rèn)會按某個功能碼周期輪詢。我第一次用它時犯過一個低級錯誤沒注意默認(rèn)的輪詢功能碼是FC01讀線圈結(jié)果連上一個只支持保持寄存器的設(shè)備界面上滿屏的讀取失敗提示。后來養(yǎng)成習(xí)慣每次新建連接后第一件事就是檢查Function下拉框把它切到目標(biāo)功能碼比如讀保持寄存器就選FC03讀輸入寄存器選FC04。地址和長度的設(shè)置也要說清楚。ModScan32界面上的Address通常顯示的是數(shù)據(jù)地址0起始而很多設(shè)備手冊里寫的是寄存器編號比如“40001對應(yīng)協(xié)議地址0x0000”。這兩者差1非常容易搞混。我的經(jīng)驗是拿到設(shè)備手冊先看它有沒有單獨給出一張“協(xié)議地址映射表”如果有你就按照協(xié)議地址填如果只給了寄存器編號那就要在編號基礎(chǔ)上減1再填進(jìn)ModScan32。2.3 用 Wireshark 開始抓包過濾、啟動、同步時間打開Wireshark之后先選對網(wǎng)卡。ModScan32連的是本機(jī)從站時要選回環(huán)接口名稱一般是“Loopback: lo”或“Npcap Loopback Adapter For Loopback Traffic Capture”。如果連的是局域網(wǎng)里的真實設(shè)備就選對應(yīng)的物理網(wǎng)卡。網(wǎng)卡選錯是新手期最常見的問題我看到過有人抓了一晚上只看到自己電腦的廣播包設(shè)備報文一個都沒抓到原因是電腦上有好幾塊網(wǎng)卡設(shè)備接在另一塊上。抓包啟動后立刻在過濾欄輸入tcp.port 502這樣只顯示Modbus TCP流量不會被其它噪聲干擾。如果你想更精確一點可以再加上對端IPip.addr 192.168.1.100 tcp.port 502。啟動抓包的同時讓ModScan32開始輪詢或手動觸發(fā)一次讀寫Wireshark這邊就能看到成對的請求和響應(yīng)幀了。這里還有一個細(xì)節(jié)值得養(yǎng)成習(xí)慣在Wireshark的View菜單里把時間顯示格式改成“Seconds Since Previous Displayed Frame”或者“Seconds Since First Frame”。Modbus TCP調(diào)試經(jīng)常需要看響應(yīng)時間默認(rèn)的絕對時間在判斷超時問題時不夠直觀。相對時間一眼就能看出從請求發(fā)出到響應(yīng)返回隔了多少毫秒這對排查偶發(fā)性超時問題幫助很大。3. 第一次抓包從 ModScan32 到 Wireshark 的數(shù)據(jù)流3.1 讀保持寄存器FC03的完整報文拆解假設(shè)你在ModScan32里設(shè)置功能碼FC03起始地址0長度10個寄存器并且讓模擬從站把這些寄存器的值設(shè)置成已知的數(shù)。點擊讀取之后Wireshark里會先出現(xiàn)一條從主站發(fā)往502端口的請求幀。展開這條TCP報文的應(yīng)用層負(fù)載默認(rèn)情況下Wireshark已經(jīng)幫你解析成了Modbus協(xié)議字段。先看請求部分的字節(jié)構(gòu)成一個典型的FC03請求是這樣12個字節(jié)的十六進(jìn)制數(shù)據(jù)00 01 00 00 00 06 01 03 00 00 00 0A逐段拆開看前兩個字節(jié)00 01是事務(wù)處理標(biāo)識符用于關(guān)聯(lián)請求和響應(yīng)接著00 00是協(xié)議標(biāo)識符Modbus協(xié)議規(guī)定這個字段為0再接著00 06是后續(xù)字節(jié)的長度它表示從單元標(biāo)識符到數(shù)據(jù)末尾一共6個字節(jié)然后是01單元標(biāo)識符再往后的03是功能碼表示“讀保持寄存器”00 00是起始寄存器地址00 0A是寄存器數(shù)量十進(jìn)制就是10。再看響應(yīng)正常情況下是這樣一組字節(jié)00 01 00 00 00 17 01 03 14 C0 A1 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F和請求對應(yīng)事務(wù)標(biāo)識符00 01和請求相同說明這是那一次請求的響應(yīng)。00 00協(xié)議標(biāo)識符不變00 17是長度十進(jìn)制23代表從單元ID開始有23個字節(jié)。01單元ID、03功能碼都和請求一致緊接著的14是數(shù)據(jù)字節(jié)數(shù)十進(jìn)制20表示后面跟著20個字節(jié)的寄存器值每個寄存器2個字節(jié)正好對應(yīng)請求里10個寄存器的長度。我第一次把這兩幀放在一起看的時候整個Modbus協(xié)議一下子“通”了。所謂通訊協(xié)議本質(zhì)上就是對字節(jié)的約定你按約定發(fā)出請求對方按約定回數(shù)據(jù)。Wireshark把這些約定翻譯成了人話但你如果能自己看懂原始十六進(jìn)制就能脫離Wireshark自帶的解析器直接判斷是不是解析器本身識別錯誤的問題。3.2 寫多寄存器FC16的完整報文拆解讀操作只是請求響應(yīng)兩個半場寫多寄存器FC16則會涉及更長的數(shù)據(jù)段。假設(shè)要通過ModScan32往從站地址0和1寫入兩個寄存器的值設(shè)置數(shù)據(jù)分別為0x1234和0xABCD抓包會看到這樣一幀請求00 02 00 00 00 0B 01 10 00 00 00 02 04 12 34 AB CD對照解析00 02是事務(wù)ID00 00協(xié)議ID00 0B長度十進(jìn)制11表示從單元ID起有11個字節(jié)01單元ID10就是十進(jìn)制16即FC16寫多寄存器。00 00起始地址00 02要寫的寄存器數(shù)量04表示后面數(shù)據(jù)總字節(jié)數(shù)最后12 34 AB CD就是按順序?qū)懭雰蓚€寄存器的值。對應(yīng)響應(yīng)幀通常是00 02 00 00 00 06 01 10 00 00 00 02注意FC16的正常響應(yīng)不回寫數(shù)據(jù)內(nèi)容它只回顯起始地址和寄存器數(shù)量告訴主站“我寫成功了”。如果從站返回的功能碼變成了0x90即10 | 0x80那就說明這是一條異常響應(yīng)后面跟的異常碼會解釋拒絕原因。這個設(shè)計在R/W調(diào)試時特別值得記住不要指望寫操作響應(yīng)里帶回你剛寫入的數(shù)據(jù)想看寫沒寫對要么再發(fā)一次讀請求要么就在從站那邊確認(rèn)。3.3 單線圈操作FC05的寫值細(xì)節(jié)單線圈寫操作FC05和寄存器寫操作有一個容易栽跟頭的區(qū)別寫線圈時Open和Close對應(yīng)的不是0和1而是0xFF00和0x0000。某個從站如果對寫入值的檢查很嚴(yán)格你發(fā)00 01它可能不認(rèn)只有完整的0xFF00它才認(rèn)為是“置位”。ModScan32在界面上做單線圈寫的時候通常只是勾選一個復(fù)選框你根本不會去關(guān)心底層字節(jié)。但在分析Wireshark抓包時如果看到某條FC05請求的數(shù)據(jù)段是兩個字節(jié)的FF 00不要覺得奇怪這是Modbus協(xié)議定義的標(biāo)準(zhǔn)寫法。而且FC05的響應(yīng)是請求的完整回顯功能碼、地址、值全部原樣返回說明沒有異常。從抓包驗證的角度你甚至可以把響應(yīng)和請求逐字節(jié)比對確認(rèn)從站確實收到了正確內(nèi)容。從這個層面說ModScan32幫你生成了合規(guī)的請求幀而Wireshark幫你確認(rèn)了請求幀在網(wǎng)絡(luò)上是什么樣子。二者配合起來你就能逐步建立“界面操作—字節(jié)轉(zhuǎn)換—協(xié)議語義”三者之間的對應(yīng)關(guān)系這比背協(xié)議文檔有效得多。4. 深層報文分析從字節(jié)里讀出問題4.1 MBAP 頭部與協(xié)議字段含義Modbus TCP的應(yīng)用層數(shù)據(jù)分為兩個部分前面7個字節(jié)是MBAP頭Modbus Application Protocol Header后面是PDU協(xié)議數(shù)據(jù)單元。MBAP頭的存在是Modbus TCP相比Modbus RTU最大的區(qū)別。RTU有地址域和CRC校驗TCP則用MBAP頭來替代因為TCP和IP層已經(jīng)解決了尋址和校驗的問題。MBAP頭的7個字節(jié)我習(xí)慣按四個字段記Transaction Identifier事務(wù)ID2字節(jié)用來把請求和響應(yīng)配對Protocol Identifier協(xié)議ID2字節(jié)在標(biāo)準(zhǔn)Modbus里恒為0Length長度2字節(jié)表示它后面所有字節(jié)的數(shù)量即Unit ID加PDU的總長Unit ID單元標(biāo)識1字節(jié)用來標(biāo)識設(shè)備號在TCP直接連接一臺從站時一般填1但經(jīng)過網(wǎng)關(guān)轉(zhuǎn)換再下掛串口設(shè)備時這里的意義就變成了鏈路后方的設(shè)備地址。事務(wù)ID這個字段在調(diào)試高并發(fā)上位機(jī)時值得多留意。如果你的上位機(jī)用多線程同時發(fā)請求然后靠“先收到的響應(yīng)就丟給當(dāng)前等待的線程”那事務(wù)ID就完全對不上數(shù)據(jù)全亂。正確做法是每個請求分配一個事務(wù)ID響應(yīng)回來時按事務(wù)ID匹配。Wireshark的Modbus解析器非常貼心它會直接顯示請求和響應(yīng)的Transaction ID是否一致你能很直觀地看出亂七八糟的響應(yīng)到底對應(yīng)哪個請求。4.2 PDU 中功能碼和數(shù)據(jù)字段詳解PDU的結(jié)構(gòu)比MBAP更簡潔功能碼一個字節(jié)后面跟著數(shù)據(jù)。功能碼決定了整個PDU的語義和格式。0x01讀線圈、0x02讀離散輸入、0x03讀保持寄存器、0x04讀輸入寄存器、0x05寫單線圈、0x06寫單寄存器、0x0F寫多線圈、0x10寫多寄存器。還有0x17讀/寫多寄存器等組合操作但在儀表、傳感器類設(shè)備里出鏡率沒那么高。異常響應(yīng)是PDU里一個需要特別熟悉的形式。當(dāng)從站無法處理請求時它會把功能碼的最高位置1比如FC03變成0x83FC16變成0x90然后數(shù)據(jù)段第一個字節(jié)就是異常碼。異常碼各有含義01是非法功能說明從站根本不支持這個功能碼02是非法數(shù)據(jù)地址說明地址超范圍或不存在03是非法數(shù)據(jù)值說明數(shù)據(jù)本身不合法比如寫線圈時寫了0x0001而不是0xFF0004是從站設(shè)備故障設(shè)備內(nèi)部異常06是從站忙稍后再試。實際調(diào)試中異常的判斷價值很大。假設(shè)你向一臺只支持FC03和FC04的設(shè)備發(fā)FC06寫單個寄存器Wireshark里會看到請求正常發(fā)出響應(yīng)卻返回0x86和異常碼01。你立刻就能知道是這個功能碼不被支持而不是地址或數(shù)據(jù)值的問題。如果沒有Wireshark你可能會在ModScan32界面上反復(fù)調(diào)整寄存器地址怎么試都不成功白白浪費時間。4.3 字節(jié)序、寄存器映射與地址偏移的坑字節(jié)序是Modbus調(diào)試?yán)镒罱?jīng)典的坑之一。Modbus協(xié)議本身規(guī)定寄存器數(shù)據(jù)按大端傳輸即高字節(jié)在前、低字節(jié)在后。比如寄存器值0x1234在網(wǎng)絡(luò)上先傳0x12再傳0x34。這本身沒有歧義但問題出在當(dāng)寄存器數(shù)據(jù)被轉(zhuǎn)換到16位以上的數(shù)值時——比如32位浮點數(shù)、32位整數(shù)不同廠家對“兩個字寄存器在內(nèi)存里的排列順序”的理解不一樣。我一個實際項目里遇到過這樣的情況某設(shè)備的32位浮點參數(shù)在設(shè)備說明書里標(biāo)著“使用兩個連續(xù)保持寄存器”但沒有寫字節(jié)序。ModScan32讀回來后按默認(rèn)的“前寄存器存高16位”格式顯示數(shù)值完全不對。我試著把顯示格式切到“前寄存器存低16位”數(shù)值瞬間變成合理的工程量。而這在Wireshark里看原始字節(jié)是完全一樣的區(qū)別只在于你如何解釋這兩個寄存器。所以判斷字節(jié)序唯一可靠的方法是先把一個已知值寫入從站再用Wireshark看原始字節(jié)然后結(jié)合你想要的數(shù)值反推排列方式。別信廠家的含糊敘述也先別急著改代碼先用工具把字節(jié)序摸清楚后面寫上位機(jī)時直接在協(xié)議層做字節(jié)交換就行。另一個同樣坑的是地址偏移。很多設(shè)備的“寄存器號”從1開始編號比如40001、40002而協(xié)議報文里的地址從0開始。ModScan32界面上填1和填0會讓請求里的地址字段差一個。我用Wireshark看過一個實際案例工程師在ModScan32里填了地址40001界面提示“地址無效”他改填40000才成功。但抓包一看報文里的地址字段其實一直是0x9C41因為界面上顯示的地址和線纜上的地址根本是兩套編碼方式。讀協(xié)議文檔時一定要看它給的是“Modbus地址”還是“寄存器編號”這兩個差一分請求就偏離一分。5. 調(diào)試中常見的異常與排查5.1 連接建立但數(shù)據(jù)不對優(yōu)先檢查功能碼、地址和長度現(xiàn)象是ModScan32能連上從站但讀到的寄存器要么全0要么是明顯不可能出現(xiàn)的值。這時候別急著懷疑設(shè)備壞了先回看Wireshark里的請求幀依次核對三個要素功能碼是否對應(yīng)你要讀的區(qū)域、起始地址是否落在設(shè)備支持的范圍內(nèi)、長度是否跨出了有效區(qū)。舉個例子有的設(shè)備保持寄存器地址范圍是0到49總共50個寄存器。你在ModScan32里填了起始地址48長度10那么實際請求會要求讀地址48到57后面8個地址超出了設(shè)備范圍。多數(shù)從站會返回異常碼02但也有一些實現(xiàn)不完善的從站直接回一個“能讀多少讀多少”的截斷響應(yīng)甚至返回全0。遇到這種情況抓包能立即看到問題出在請求組裝的越界上不是你程序里的錯。如果功能碼和地址看起來都對數(shù)據(jù)依然不對那就要考慮數(shù)據(jù)類型解釋的問題。ModScan32界面上有數(shù)據(jù)格式選項可以把數(shù)據(jù)顯示成十六進(jìn)制、十進(jìn)制、浮點等。不要直接在十進(jìn)制顯示下判斷“數(shù)值不對”先切到十六進(jìn)制看原始寄存器值再結(jié)合設(shè)備手冊理解每個位和字節(jié)的含義。5.2 從站有響應(yīng)但界面空白檢查異常幀還有一種情況比較隱蔽ModScan32界面上某個地址格子里一直空白好像從站沒響應(yīng)。但Wireshark明明抓到了響應(yīng)幀而且響應(yīng)里帶著功能碼和異常碼。為什么ModScan32不顯示異常信息因為它的顯示區(qū)設(shè)計比較簡單有些異常它只會把這個格子的數(shù)據(jù)置為無效不會彈一個窗口告訴你“這次請求被從站拒絕了”。所以當(dāng)你發(fā)現(xiàn)ModScan32某些格子讀不到數(shù)據(jù)時立刻按CtrlF在Wireshark里過濾modbus.func_code 0x80把所有異常響應(yīng)一次性列出來。異常碼會直接告訴你從站拒絕請求的原因。這里我想強(qiáng)調(diào)一下“非法數(shù)據(jù)地址”和“非法功能”的區(qū)別如果請求的功能碼本身不在從站支持列表里回的是01如果功能碼支持但地址超范圍回的是02。這兩個信息對應(yīng)的排查方向完全不同別混在一起。還有一點ModScan32的輪詢速度可能很快從站如果因為忙沒來得及處理會回異常碼06。這種情況Wireshark里能看到響應(yīng)時間很短但帶異常說明是從站太慢。遇到這類從站ModScan32的輪詢間隔就要調(diào)大一些否則正常的寄存器讀寫也可能被誤判為通信失敗。5.3 Wireshark 看不到 Modbus 解析端口和字節(jié)問題Wireshark不能自動識別Modbus協(xié)議通常是這幾個原因。最普遍的是端口不是502。Modbus TCP規(guī)范默認(rèn)跑在TCP 502端口Wireshark的解析器默認(rèn)也認(rèn)這個端口。如果你的上位機(jī)、網(wǎng)關(guān)或測試工具用了別的端口比如5020Wireshark就不會把它當(dāng)Modbus顯示只會看到一個TCP負(fù)載。解決辦法有兩種一是在過濾欄里等TCP流建立后右鍵任意一條TCP報文選擇“Decode As”把該端口或該TCP流的協(xié)議強(qiáng)制設(shè)為Modbus TCP二是讓服務(wù)端保持用502端口客戶端可以隨便只要抓包時Decode As設(shè)置好就能解決。另一種情況是數(shù)據(jù)被拆分或合并。TCP是流式傳輸不是消息邊界分明的UDP。一個Modbus請求雖然只有十幾個字節(jié)但可能被TCP層拆成兩個段發(fā)送也可能兩個小請求被合并到一個段里。Wireshark的Modbus解析器處理這種情況時如果只是設(shè)置了Decode As會有概率解析不出來。這時候可以右鍵第一條報文選擇“Follow TCP Stream”在流內(nèi)容里肉眼找MBAP頭的特征字節(jié)比如協(xié)議ID00 00和長度字段手動判斷應(yīng)用層的邊界。如果連原始字節(jié)都看不到那問題就出在抓包的位置或網(wǎng)卡上。我記得前面反復(fù)強(qiáng)調(diào)過回環(huán)網(wǎng)卡這個問題真機(jī)調(diào)試時還會遇到交換機(jī)鏡像、無線抓包等更復(fù)雜的環(huán)節(jié)但基本排查思路是一致的要么離設(shè)備更近要么確認(rèn)鏈路中間沒有把包改寫。5.4 響應(yīng)超時和重試問題先看時間戳再下結(jié)論Modbus TCP的超時問題通常有兩種表象ModScan32提示通信超時或者上位機(jī)頻繁報超時。這時候Wireshark里看時間戳最有說服力。請求發(fā)出后如果過了很長時間才收到響應(yīng)那就是從站處理慢或者網(wǎng)絡(luò)鏈路質(zhì)量差如果壓根沒看到響應(yīng)那就是請求根本沒到達(dá)從站或從站的響應(yīng)沒回來。判斷這個有個小技巧打開Wireshark的“Statistics - Flow Graph”功能選擇顯示TCP流。界面里會按時間軸列出每個段的發(fā)送方向、相對時間。第一次看到這種圖時你會非常直觀地感受到“原來響應(yīng)等了這么久”或者“原來重試風(fēng)暴是先請求超時后立刻重發(fā)積壓出來的”。在排查程序里的超時重試邏輯時我經(jīng)常靠這個視圖確認(rèn)重試間隔設(shè)置得是否合理。另外要留意TCP層的重傳。如果Wireshark里出現(xiàn)大量TCP Retransmission說明鏈路丟包比較嚴(yán)重此時Modbus應(yīng)用層的超時只是表象真正要修的是網(wǎng)絡(luò)質(zhì)量或者TCP參數(shù)。這個思路可以避免你陷入無休止地調(diào)Modbus超時時間的死循環(huán)。6. 實戰(zhàn)心得與建議6.1 關(guān)于輪詢周期和流量控制的取舍ModScan32的輪詢間隔最小能設(shè)到很低但現(xiàn)實是遠(yuǎn)程設(shè)備或網(wǎng)關(guān)經(jīng)不起你這么快的輪詢。有些串口服務(wù)器把Modbus TCP轉(zhuǎn)成RTU時串口波特率只有9600一個帶有10個寄存器的讀請求加上等待時間至少要幾十毫秒。如果你上位機(jī)上用ModScan32測試時把輪詢設(shè)得飛快看起來沒問題等到現(xiàn)場大量真實請求并發(fā)時就容易撞車。抓包時你會觀察到連續(xù)的Modbus TCP請求經(jīng)常出現(xiàn)包被合并的現(xiàn)象這就是TCP層Nagle算法和延遲確認(rèn)在起作用。短請求多的情況下這種合并會降低應(yīng)用層的實時性。如果上位機(jī)程序?qū)憫?yīng)時間敏感可以考慮在TCP層關(guān)閉Nagle算法但ModScan32本身沒有這個開關(guān)所以你只能在抓包分析時把它當(dāng)成一個“為什么會慢”的解釋維度。6.2 真實設(shè)備場景下抓包的一點經(jīng)驗真機(jī)調(diào)試和模擬器最大的區(qū)別是你不再擁有對從站內(nèi)存和行為的完全控制。真實設(shè)備可能掛在現(xiàn)場總線上上游還有PLC、網(wǎng)關(guān)、觸摸屏同時訪問它。這時候用Wireshark抓包要注意過濾條件不要只寫tcp.port 502最好加上目標(biāo)設(shè)備的IP或者網(wǎng)關(guān)的IP否則同一網(wǎng)絡(luò)上其它Modbus設(shè)備的數(shù)據(jù)會把你的分析淹沒。如果你在電腦上直接連設(shè)備抓到的包實際上經(jīng)過了你的網(wǎng)卡和交換機(jī)能看到的內(nèi)容取決于抓包點的位置。能抓到主站發(fā)出的請求、但看不到從站響應(yīng)通常說明從站響應(yīng)被交換機(jī)某個端口隔離了兩邊都能看到但內(nèi)容不一致那就要考慮鏡像口配置或者中間網(wǎng)關(guān)的透明轉(zhuǎn)發(fā)問題。抓包分析很像照CT你得知道自己在哪個層面看才能判斷影像是真實的還是誤差的。6.3 一點個人體會用ModScan32加Wireshark這套組合已經(jīng)好幾年最大的體會是協(xié)議調(diào)試難往往不是難在對協(xié)議不熟而是難在“信息太少”和“信息太多”兩個極端之間。沒有Wireshark時你只有一個黑盒界面出了問題只能靠猜有了Wireshark你又會面對成百上千幀報文如果不會篩選和拆解反而更容易迷失。務(wù)實的做法是先控制變量把請求限制成固定IP、固定功能碼、少量寄存器讓人和機(jī)器都能看清一個完整的交互再逐漸往復(fù)雜場景擴(kuò)展。我在實際調(diào)試設(shè)備時有一個固定的流程至今沒有變過先用自己的主站工具讀一次Wireshark看一次原始請求再用從站手冊里的標(biāo)準(zhǔn)報文手工構(gòu)造一個請求主動發(fā)到設(shè)備上看它的響應(yīng)最后才編寫上位機(jī)代碼。三步走下來十個問題里能提前規(guī)避九個。剩下一個偶發(fā)問題大概率是現(xiàn)場網(wǎng)絡(luò)環(huán)境的干擾那時候帶上Wireshark到現(xiàn)場再看一次也能順利定位。最后再分享一個小技巧如果你在ModScan32和現(xiàn)場設(shè)備之間串了一臺電腦做透明轉(zhuǎn)發(fā)可以用它做“雙頭抓包”對比分析一頭看主站發(fā)的、一頭看設(shè)備收的。兩邊報文不一致問題肯定在中間。配合Wireshark的導(dǎo)出報文功能把對比結(jié)果保存下來回頭發(fā)給協(xié)議棧或者網(wǎng)關(guān)廠商他們也能很快明白問題所在。這套方法幫我省下過很多次現(xiàn)場返工的時間也希望能在你的項目里幫上忙。