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

SNMP4J包 TCP-消息发送时的一个BUG

程序员文章站 2022-04-13 16:56:51
...

在实际使用中,发现这样的一个问题,客户端(工业级交换机)和本地服务端的连接总是中断,当然不是超时自动清理的。

这个中断,有时可以建立上并能正常使用,有时连接上了却不正常使用这个连接。后台日志打印,明明连接已经存在却一直报错说是通道已经关闭。

 

经过多次测试和断点跟踪,发现交换机在一定时间不实用的话就认定连接中断,马上进行连接重建,而此时如果服务端正在给客户端发送消息,就会在一定几率上造成连接建立却无法继续使用的情况

发生这种情况时,会一直在某个地方报错连接通道已经关闭,DefaultTcpTransportMapping 内部类 ServerThread 的 processPending 方法:

private void processPending() {
  synchronized (pending) {
	for (int i=0; i<pending.size(); i++) {
	  SocketEntry entry = (SocketEntry)pending.getFirst();
	  try {
		// Register the channel with the selector, indicating
		// interest in connection completion and attaching the
		// target object so that we can get the target back
		// after the key is added to the selector's
		// selected-key set
		if (entry.getSocket().isConnected()) {
		  entry.getSocket().getChannel().register(selector,
			  SelectionKey.OP_WRITE);
		}
		else {
		  entry.getSocket().getChannel().register(selector, SelectionKey.OP_CONNECT);
		}
	  }
	  catch (IOException iox) {
		logger.error(iox);
		// Something went wrong, so close the channel and
		// record the failure
		try {
		  entry.getSocket().getChannel().close();
		  TransportStateEvent e =
			  new TransportStateEvent(DefaultTcpTransportMapping.this,
									  entry.getPeerAddress(),
									  TransportStateEvent.STATE_CLOSED,
									  iox);
		  fireConnectionStateChanged(e);
		}
		catch (IOException ex) {
		  logger.error(ex);
		}
		lastError = iox;
		if (SNMP4JSettings.isFowardRuntimeExceptions()) {
		  throw new RuntimeException(iox);
		}
	  }
	}
  }
}

 

可以看到他发生异常后,就会把通道的连接关闭,然后进行一次状态改变事件的通知。

可是在交换机重建连接后应该是马上可以使用的,为什么会报错通道不可用呢?

 

我开始认为是某些异常处理时把通道给关闭了,但是经过跟踪和测试应该不是这个问题。

看到他是在操作 pending 这个对象,而且是线程安全的,所以我跟踪了下关于这个对象的操作,他的定义是这样的:

private LinkedList pending = new LinkedList();

 

是个集合,往集合里面增加东西是在消息发送时操作的:

public void sendMessage(Address address, byte[] message)  throws java.io.IOException  {
  Socket s = null;
  SocketEntry entry = (SocketEntry) sockets.get(address);
  if (logger.isDebugEnabled()) {
	logger.debug("Looking up connection for destination '"+address+ "' returned: "+entry);
	logger.debug(sockets.toString());
  }
  if (entry != null) {
	s = entry.getSocket();
  }
  if ((s == null) || (s.isClosed()) || (!s.isConnected())) {
	if (logger.isDebugEnabled()) {
	  logger.debug("Socket for address '"+address+  "' is closed, opening it...");
	}
	pending.remove(entry);
	SocketChannel sc = null;
	try {
		// Open the channel, set it to non-blocking, initiate connect
		sc = SocketChannel.open();
		sc.configureBlocking(false);
		sc.connect(new InetSocketAddress(((TcpAddress)address).getInetAddress(), ((TcpAddress)address).getPort()));
		s = sc.socket();
		entry = new SocketEntry((TcpAddress)address, s);
		entry.addMessage(message);
		sockets.put(address, entry);
		synchronized (pending) {
		  pending.add(entry);
		}
		selector.wakeup();
		logger.debug("Trying to connect to "+address);
	}
	catch (IOException iox) {
	  logger.error(iox);
	  throw iox;
	}
  }
  else {
	entry.addMessage(message);
	synchronized (pending) {
	  pending.add(entry);
	}
	selector.wakeup();
  }
}

 

跟踪断点会发现,如果客户端不可用的情况下你发送消息,他会进if ((s == null) || (s.isClosed()) || (!s.isConnected())) 的判断区域,那么这个区域的代码执行的操作却是建立针对这个地址建立一个客户端连接然后发送数据。

这显然不对,而且因为他建立连接时正好处于交换机的重启或连接重建时,不会报错,所以就会造成这个消息被当成自己是客户端对方是服务端给处理了。

而执行 processPending 方法时,正好在 entry.getSocket().getChannel().close(); 之前交换机的客户端连接过来了,连接刚刚建立这里就执行了entry.getSocket().getChannel().close();  你再使用这个连接发送消息,就会一直提示通道已经关闭!

 

我的建议是,这里不应该认定没有连接存在就把这个消息当客户端消息给发送,应该是如果不是服务端再进行上面说的判断区域。

而且DefaultTcpTransportMapping 类中有是否是服务端的属性:

private boolean serverEnabled = false;

 

如果你是服务端,那么这个属性就是 TRUE 。

 

所以消息发送时应该是这样判断:

// TODO 如果本地不是服务端,则创建连接再发送
if (!serverEnabled) {

 

如果你的消息都是当成服务端去发送了,那么这个连接一旦建立成功,且此时程序还在处理中,那么消息照样是发送成功的!

 

请您到ITEYE网站看原创,谢谢!

http://cuisuqiang.iteye.com/ ! 

自建博客地址:http://www.javacui.com/ ,内容与ITEYE同步!