原来设计这个模块的时候没怎么参与,只是建议才用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, 在大数量设备的时候应该很有优势。