原來設計這個模塊的時候沒怎么參與,只是建議才用snmp4j, 因為負責這個模塊的小兄弟以前玩joesnmp的,所以建議沒被采用,直到這位兄弟離職,我開始測試整個系統的效率,才開始關注到這里來。
1. snmp模擬器
做效率測試要模擬設備,記得以前公司做模擬器專門有兩個人,做的花里胡哨。上網搜了一下,專業的snmp模擬器是收費的。算了自己動手吧。
模擬器無非收到請求,模擬設備響應返回。做效率測試的時候要求多臺滿配置的設備,這里主要是想辦法把一套設備模擬成無窮多套設備。那么模擬數據哪里來呢?手工添加?工作量很大,而且容易出錯。這里考慮到代理方式,把一個真實的設備幻影成無數想同的設備。實現很簡單,對某個snmp請求緩存下來,下次請求直接從緩存中返回。這里有個前提,就是假設相同OID的響應是不變的, 對于記數器等特殊OID是不行的。對于set操作,直接下發。
一般snmp的訪問端口是161, trap端口162, 在模擬器上不能遵守這個規則,幸好一般SNMP工具和api都支持端口選擇。
另外一個問題,代理的緩存級別在哪里? 對每個OID緩存?這樣要解析每個get/set,還是直接對每個二進制報文緩存? 帶著問題,先來研究一下SNMP的解構,看看哪些地方可以利用。
這里碰到個小插曲,snmp源碼不在cvs上,每個人指定的source在會不一樣,需要自己配置很麻煩。就干脆把源碼也拖到jar里面,eclipse自動識別,很爽。
Snmp4j的幾個主要類:
TransportMapping.java //傳輸層的封裝,對UDP封裝為DefaultUdpTransportMapping,TCP封裝為DefaultTcpTransportMapping. SNMP4J的最底層勞動人民。
? .sendMessage(Address address, byte[] message); //發送數據報文,byte[]
? .addTransportListener(TransportListener); //監聽數據報文
? .listen();
MessageDispatcher extends TransportListener//Snmp4j的中間層,對上負責編碼PDU為二進制報文發送。對下實現了報文監聽接口,編碼收到的二進制報文為PDU。主要實現:MessageDispatcherImpl/MultiThreadedMessageDispatcher(多線程的消息處理)。SNMP的不同版本區分主要體現在這個翻譯層。
? .addMessageProcessingModel(MessageProcessingModel model) //加入不同版本的消息處理器。主要實現就是MPv1,MPv2c,MPv3
? .addCommandResponder(CommandResponder listener) //上層同過這里監聽底層消息,注意CommandResponder處理的是PDU
? .getNextRequestID():int; //負責給報文編流水號
? .processMessage(TransportMapping,Address incomingAddress,ByteBuffer);//TransportListener的實現,處理底層二進制數據。
? .sendPdu(...,PDU,...); //編碼并發送報文。
Snmp.java //SNMP主要的對外操作類,類似一個session,完成常用任務。
? .get(PDU,Target); //同步發送, SNMP4j封裝了同步調用,比JoeSNMP省事不少。
? .get(PDU,Target,Object,ResponseListener); //異步發送
? .getBulk(...),getNext(...),set(...);//同上,對SNMP操作的直接封裝
? .inform(...),trap(...);//同上,用于agent時,對外訂閱者發送trap
? .send(PDU,Target); //底層的同步發送,內含超時等待機制, 當前線程會被block
? .send(PDU,Target,Object,ResponseListener); //底層的異步發送, 內含超時機制。
? .addCommandResponder(CommandResponder); //添加全局的trap處理
? .addNotificationListener(Address,CommandResponder); //監聽某設備的trap
? .listen(),close();//開始,結束session. 注意,沒有listen是收不到設備響應的,因為udp是異步的。
PDU.java implements BERSerializable //SNMPv2的報文,提供了編碼時需要的信息(個人覺得編碼信息可以由工具類提供,對用戶會混淆)。報文結構參見http://xinwang.shanghaitelecom.com.cn/xinwangbu/show.php?newsid=146。主要子類PDUv1, ScopedPDU(v3)。
Address.java //IP地址和端口(和java.net的不同), 常用實現是UdpAddress
Target.java //發送的時候要用到,包含Address,超時,重試次數,snmp版本, 常用實現是ComunityTarget,可以指定community
通常想法是在頂層Snmp這層做模擬,因為模擬器相當于agent, 接收get/set請求同過CommandResponder取得,對一些不緩存的請求可以通過這種方式放過去。
考慮到需求比較簡單,從第一層入手即可,對收到的所有請求,僅僅解碼報頭部分,對于get類操作,所有type+報文體作為key.不用管里面是什么請求。
實現注意要點:
1. 請求的requestID和轉發出去的不同
2. 因為ber編碼是變長的(不可思議啊),不同的requestID可能導致報文的長度變化,幸好requestID在開頭,開頭這里重新編碼一下搞定。
3. trap模擬用戶段稍微麻煩,以前通過IP來判斷設備的方法行不通,需要增加端口來識別。
JoeSnmp和Snmp4J的區別:
1. 前者相對簡單,沒有同步請求的報裝。
2. Snmp4J則考慮到多線程發送和接收模型.
3. 前者一個Session只能對應一個設備,在設備很多的時候開銷比較大。
4. Snmp4J的一個Session可以對多個設備發送/接收.
5. 前者目前還不支持snmpv3
在效率分析過程中發現代碼中對JoeSnmp的調用是對每個request構造一個session,重構為session緩存,session/device, 效率提高10倍以上。
以后如果改用snmp4j, 在大數量設備的時候應該很有優勢。
?