October 10, 2022
By: Kevin
从wireshark抓取数据帧结合代码调试的完整流程
- 1. Wireshark 抓包并复制十六进制
- 2. 字符串 → byte[]
- 3. 统一到 byte-streams 生态
- 4. 用 Gloss 描述协议
- 5. 解码并转成业务 Map
- 6. 快速 REPL 验证
- 7. 单元测试示例
- 8. 踩坑与优化
- 9. 小结
本文记录一次从 Wireshark 抓包到 Clojure Gloss 解码的完整实践, 帮助你快速把十六进制流解析成结构化数据. 全流程仅依赖
clj-commons/byte-streams与gloss, 其余全部使用 JDK 自带功能.
1. Wireshark 抓包并复制十六进制
- 在 Wireshark 里选中一条 TCP 报文.
- 在TCP的数据(TCP Segment data)上右键 → Copy → as Hex Stream.
- 得到一段纯十六进制字符串, 例如:
ff000000000000000000000000000000000000000000387cd6ea37fcd6ea387cd6ea41c73d3041b602d041ab54003f62da13000000000000000037fcd6ea41afcdee000000003f49638f3d81a12f
粘到 Clojure REPL 或编辑器备用.
2. 字符串 → byte[]
2.1 推荐方案: JDK 17+ java.util.HexFormat
(import '[java.util HexFormat]
'[clojure.string :as str])
(def hex-str "...上面的字符串...")
(def bytes (.parseHex (HexFormat/of)
(str/replace hex-str #"\s+" "")))
;; bytes 现在是真正的 Java byte[]
- 零依赖, 速度快.
- 要求 JDK ≥ 17.
2.2 旧版 JDK 替代
(defn hex->byte-array
"Parse a pure hex string (ignore any whitespace) into a Java byte[]."
^bytes
[^String s]
(let [clean (str/replace s #"[^0-9A-Fa-f]" "") ; 去掉空格或其它非16进制字符
n (count clean)]
(when (odd? n)
(throw (ex-info "Hex string length must be even" {:len n})))
(byte-array ; 最终构造 byte[]
(for [i (range 0 n 2)]
(unchecked-byte
(Integer/parseInt (.substring clean i (+ i 2)) 16))))))
3. 统一到 byte-streams 生态
(require '[byte-streams :as bs])
(def ba (bs/to-byte-array bytes)) ; ba 就是你后面要喂给 Gloss 的东西
bs/to-byte-array 会探测类型, 已是 byte[] 时不会复制.
4. 用 Gloss 描述协议
(require '[gloss.core :as g])
(g/defcodec frame-synthesis
[:ubyte :ubyte :ubyte :ubyte :ubyte :ubyte :ubyte :ubyte :ubyte :ubyte
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be
:float32-be])
:ubyte描述单字节无符号整数.:float32-be描述大端 32-bit 浮点数.- 其余型号(裂解、天马合成)只要再
g/defcodec一次即可.
5. 解码并转成业务 Map
(require '[gloss.io :as gio])
(defn parse-synthesis
[bs]
(let [[_FF m20 m21 m22 m23 m24 m25 m26 m27
inverter1-actual-frequency
inverter2-actual-frequency
inverter3-actual-frequency
flowmeter1-instantaneous-flow
flowmeter2-instantaneous-flow
flowmeter3-instantaneous-flow
temperature1
temperature2
temperature3
pressure1
inverter4-actual-frequency
flowmeter4-instantaneous-flow
flowmeter5-instantaneous-flow
temperature4
pressure2
pressure3
pressure4] (gio/decode frame-synthesis bs)]
;;变量名直接在这个位置进行更改
(merge {:inverter1-actual-frequency inverter1-actual-frequency
:inverter2-actual-frequency inverter2-actual-frequency
:inverter3-actual-frequency inverter3-actual-frequency
:flowmeter1-instantaneous-flow flowmeter1-instantaneous-flow
:flowmeter2-instantaneous-flow flowmeter2-instantaneous-flow
:flowmeter3-instantaneous-flow flowmeter3-instantaneous-flow
:temperature1 temperature1
:temperature2 temperature2
:temperature3 temperature3
:pressure1 pressure1
:inverter4-actual-frequency inverter4-actual-frequency
:flowmeter4-instantaneous-flow flowmeter4-instantaneous-flow
:flowmeter5-instantaneous-flow flowmeter5-instantaneous-flow
:temperature4 temperature4
:pressure2 pressure2
:pressure3 pressure3
:pressure4 pressure4}
(parse-m20 m20)
(parse-m21 m21)
(parse-m22 m22)
(parse-m23 m23)
(parse-m24 m24)
(parse-m25 m25)
(parse-m26 m26)
(parse-m27 m27))))
如果需要解析位域, 建议把 m20、m21 等字节交给一个辅助函数:
(defn byte->bits [b]
(reverse
(map #(pos? (bit-and b (bit-shift-left 1 %)))
(range 8))))
然后用 zipmap 把布尔位映射到含义即可.
6. 快速 REPL 验证
(comment
(def ba (-> hex-str
(str/replace #"\s+" "")
(.parseHex (HexFormat/of))))
(gio/decode frame-synthesis ba))
确认字段数值与现场仪表一致后, 再集成到生产代码.
7. 单元测试示例
(ns socket-test
(:require [clojure.test :refer :all]
[socket :as sut]))
(deftest frame-synthesis-decode-test
(let [bs (sut/hex->bytes "ff00...")]
(is (= {:temperature1 25.3
:pressure1 1.02}
(select-keys (sut/parse-synthesis bs)
[:temperature1 :pressure1])))))
8. 踩坑与优化
- 长度校验: 用
(gp/sizeof codec)动态获取字节数, 读流时不会读多或读少. - 字节序: 西门子 S7 默认大端, 但也可能小端混用, 一定跟电气工程师确认.
- 浮点精度:
float32仅 7 位有效数字, 若需要高精度, 改用:float64-be. - 性能: Gloss 内部零拷贝, 但高频场景仍建议把
byte[]缓存到对象池.
9. 小结
- Wireshark 复制 Hex Stream.
HexFormat.parseHex把字符串一次性转成byte[].byte-streams统一数据流类型.gloss.core/defcodec抽象协议,gloss.io/decode得到结构化向量.- 再按业务需求映射成易用的 Clojure
map.
这样你就完成了从抓包到解码的闭环, 可以放心把解析后的数据喂给报警逻辑、监控面板或数据库. 如果还有更复杂的帧, 只需要再写一段 defcodec + parse-* 即可复用同一套流程.