欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SNMP4J包 TCP-无超时-网络中断 处理的BUG

程序员文章站 2022-04-13 17:01:27
...

记得之前说过一次关于SNMP4J 服务超时时间的问题 SNMP4J 服务端连接的超时时间  ,由于我们想保持这个连接的持续性,除非异常否则不能在服务端主动切断连接。

但是发现SNMP4J会主动丢掉一些连接,这个在日志中就能看到,这显然不合理。于是我设置了:

transport = new DefaultTcpTransportMapping((TcpAddress) listenAddress);
transport.setConnectionTimeout(0);

 

但是我还说,并不是很了解他底层到底是干嘛的!后来仔细查看了 DefaultTcpTransportMapping 这个类,发现这个超时时间,其实只是在本地作为服务端时,巡检和清除指定连接的一个条件。

在他的类中有这样的一个属性:

private long connectionTimeout = 60000;

 

可以看看这个属性是做什么用的,首先在开始监听时,启动了一个连接清理服务对象:

public synchronized void listen() throws java.io.IOException {
	if (server != null) {
		throw new SocketException("Port already listening");
	}
	serverThread = new ServerThread();
	server = SNMP4JSettings.getThreadFactory().createWorkerThread("DefaultTCPTransportMapping_" + getAddress(), serverThread,true);
	if (connectionTimeout > 0) {
		socketCleaner = SNMP4JSettings.getTimerFactory().createTimer();
	}
	server.run();
}

 

但是注意,这个对象启动的条件是你设置了超时时间,也就是connectionTimeout 大于 0 时。

往下找会找到一个内部类的子线程,他通过最后使用时间、超时时间、现在时间计算,来判定那个连接需要清理:

class SocketTimeout extends TimerTask {
	private SocketEntry entry;
	public SocketTimeout(SocketEntry entry) {
		this.entry = entry;
	}
	public void run() {
		long now = System.currentTimeMillis();
		if ((socketCleaner == null) || (now - entry.getLastUse() >= connectionTimeout)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Socket has not been used for " + (now - entry.getLastUse()) + " micro seconds, closing it");
			}
			sockets.remove(entry.getPeerAddress());
			try {
				synchronized (entry) {
					entry.getSocket().close();
				}
				logger.info("Socket to " + entry.getPeerAddress() + " closed due to timeout");
			} catch (IOException ex) {
				logger.error(ex);
			}
		} else {
			if (logger.isDebugEnabled()) {
				logger.debug("Scheduling " + ((entry.getLastUse() + connectionTimeout) - now));
			}
			socketCleaner.schedule(new SocketTimeout(entry), (entry.getLastUse() + connectionTimeout) - now);
		}
	}
	public boolean cancel() {
		boolean result = super.cancel();
		entry = null;
		return result;
	}
}

 

可以看到,在超时之后,他会关闭连接,并且执行一行代码:

sockets.remove(entry.getPeerAddress());

 

会清理掉这个连接的缓存!

问题就在这里,如果你设置了超时时间是 0 ,那么这个清理就不会执行。你会想到他会在异常时处理,那么可以看一下他的服务类 ServerThread ,有这样的一段处理代码,并且他们也加了注释:

if (readChannel != null) {
    try {
      readMessage(sk, readChannel, incomingAddress);
    }
    catch (IOException iox) {
      // IO exception -> channel closed remotely
      if (logger.isDebugEnabled()) {
        iox.printStackTrace();
      }
      logger.warn(iox);
      sk.cancel();
      readChannel.close();
      TransportStateEvent e =
          new TransportStateEvent(DefaultTcpTransportMapping.this,
                                  incomingAddress,
                                  TransportStateEvent.
                                  STATE_DISCONNECTED_REMOTELY,
                                  iox);
      fireConnectionStateChanged(e);
    }
  }

 

很明了他是想在远程异常连接关闭时做一些处理,但仅仅是做了一个状态改变的事件,并没有做移除缓存的操作。

如果进行测试,设置超时时间是 0 ,且使用工业交换机不断变换端口进行访问,发现缓存数量就一直增加。所以我的建议是,在这里增加清除某连接的缓存,很简单:

sockets.remove(incomingAddress);

 

后续: 
因为修改后会移除链路缓存,但是后来多次测试发现,出来链路中断会在这里抛异常,垃圾数据的解析也会在这里抛异常。 

BER解析消息长度的解析中就会报错,解析代码: 

Java代码   SNMP4J包 TCP-无超时-网络中断 处理的BUG
            
    
    博客分类: 网络编程 snmpsnmp4jtcpsocket缓存 
  1. public static final int decodeLength(BERInputStream is, boolean checkLength) throws IOException {  
  2. int length = 0;  
  3. int lengthbyte = is.read();  
  4. if ((lengthbyte & ASN_LONG_LEN) > 0) {  
  5.     lengthbyte &= ~ASN_LONG_LEN; /* turn MSb off */  
  6.     if (lengthbyte == 0) {  
  7.         throw new IOException("Indefinite lengths are not supported");  
  8.     }  
  9.     if (lengthbyte > 4) {  
  10.         throw new IOException(  
  11.                 "Data length > 4 bytes are not supported!");  
  12.     }  
  13.     for (int i = 0; i < lengthbyte; i++) {  
  14.         int l = is.read() & 0xFF;  
  15.         length |= (l << (8 * ((lengthbyte - 1) - i)));  
  16.     }  
  17.     if (length < 0) {  
  18.         throw new IOException(  
  19.                 "SNMP does not support data lengths > 2^31");  
  20.     }  
  21. else { /* short asnlength */  
  22.     length = lengthbyte & 0xFF;  
  23. }  
  24. /** 
  25.  * If activated we do a length check here: length > is.available() -> 
  26.  * throw exception 
  27.  */  
  28. if (checkLength) {  
  29.     checkLength(is, length);  
  30. }  
  31. return length;  



如果这里报错,会在readMessage(sk, readChannel,incomingAddress)时报错,但不是链路问题,如果此时我们也安装链路中断处理就会有问题。 

因此,我把解析头的代码专门try起来,发生问题就不解析,而不是向上层报错,链路断开时还是以前一样: 
在读取消息的代码中: 

Java代码   SNMP4J包 TCP-无超时-网络中断 处理的BUG
            
    
    博客分类: 网络编程 snmpsnmp4jtcpsocket缓存 
  1. try {  
  2.     messageLength = messageLengthDecoder.getMessageLength(ByteBuffer.wrap(btnew));  
  3. catch (Exception e) {  
  4.     messageLength = null;  
  5.     logger.error(e);  
  6. }  



这个方法会调用dispatchMessage方法,这个方法也会调用解析函数,所以也要处理: 

Java代码   SNMP4J包 TCP-无超时-网络中断 处理的BUG
            
    
    博客分类: 网络编程 snmpsnmp4jtcpsocket缓存 
  1. try {  
  2.     fireProcessMessage(incomingAddress, bis);  
  3. catch (Exception e) {  
  4.     logger.error(e);  
  5. }  



我的策略是,有问题就不解决,不要向上层调用者反馈解析结果。 
但是有链路断开时再进行反馈,也好让上层就是我们修改的代码知道出问题了,从而从缓存中移除链路信息。

 

请您到ITEYE看我的原创:http://cuisuqiang.iteye.com

或支持我的个人博客,地址:http://www.javacui.com