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