Java nio服务器端对于客户端连接状态的判断
2016-08-18 15:17
886 查看
Java nio服务器端对于客户端连接状态的判断
本文将介绍一个基于Java NIO开发的TCP通讯服务器端实现,用于实时监控客户端的连接状态。因为本人第一次用Java写服务器端,甚至是第一次写服务器端程序,因此过程并不是十分顺利,也是一步一个坑在走。之前遇到一个非常棘手的问题,即如果客户端因为断电或异常终止程序时,并不会像服务器发送一个读end-stream的标记,这时,我通过在服务器端给每个客户端连接加心跳监控的方式,主动检查连接状态,当心跳超时未收到客户端回应时,即调用相应客户端的SocketChannel.Close()方法。但是当同一个客户端再次请求建立连接时,会导致服务器通讯线程崩溃。下面先介绍如何实现主动监听客户端连接状态,再注明解决上述问题的办法。
因本人算是服务器端的初学者,这套方法经测试确实可用,如有不妥之处还望轻喷~也希望能看到各位更完美的解决方案。
如果客户端异常关闭,关闭前没有主动断开TCP连接,这样服务器端无法被动获知客户端的状态。因此通过不断给客户端发送心跳包的方式来主动检测连接情况。
以下为监控线程的代码实现:
/** * TCP连接监控线程 * @author Quintus * */ public class SocketMonitor implements Runnable { /** * 通讯线程发送消息方法 */ private ISend sendFunc; /** * 通讯线程的关闭连接方法 */ private IShutDown shutDownFunc; public SocketMonitor(ISend _sendFunc, IShutDown _shutDownFunc) { sendFunc = _sendFunc; shutDownFunc = _shutDownFunc; } public void run() { //启一个Timer来监控客户端状态 Timer time = new Timer(); //每隔固定时间执行一次,例如我这里每10秒发送一次心跳包 time.schedule(new ScheduleSend(), 0,NetworkConfig.CheckTCPInterval * 1000); } class ScheduleSend extends TimerTask { public void run() { //GlobalObj.LinkMap存储所有客户端连接数据 if(GlobalObj.LinkMap != null) { Iterator iter = GlobalObj.LinkMap.entrySet().iterator(); ArrayList<SocketChannel> shutDownList = new ArrayList<SocketChannel>(); //遍历所有客户端连接 while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); SocketChannel key = (SocketChannel)entry.getKey(); //更新心跳包用时,当收到客户端回应的心跳包时再将此时间重置为0 //【注意】客户端为了回应服务器端的心跳检查,也要实现一套心跳包机制,在此略过 entry.setValue((Integer)entry.getValue() + NetworkConfig.CheckTCPInterval); //判断如果心跳包用时超过某个时限,则断开连接。例如我这里的超时时限为30秒, //也就是服务器发送三次心跳包客户端都未回应,则判断客户端已失去响应,服务器断开连接。 if((Integer)entry.getValue() >= NetworkConfig.ShutDownTime) { shutDownList.add(key); } else { S100 sendData = new S100(); sendData.value = (int)entry.getValue(); sendFunc.Send(key, sendData); } } if(shutDownList.size() > 0) { shutDownFunc.ShutDownClient(shutDownList); } } } } }
下面是需要特殊注意的问题点:
如果服务器因超时而要关闭与客户端的连接时,直接调用以下代码,当被服务器关闭连接的客户端再次请求建立连接时,会导致服务器的通讯线程崩溃。
public void CloseClient(SelectionKey _key) { try { if(_key != null) { _key.cancel(); _key.channel().close(); GlobalObj.LinkMap.remove(_key.channel()); System.out.println("断开连接:"+((SocketChannel)_key.channel()).socket().getPort()); } } catch (Exception e) { e.printStackTrace(); } }
正确的做法是当服务器判断超时而要断开与客户端的连接时,调用以下代码:
public void ShutDownClient(SocketChannel _clientSocket) { if(_clientSocket.isOpen()) { try { //将连接的输入输出都关闭,而不是直接Close连接 _clientSocket.shutdownInput(); _clientSocket.shutdownOutput(); GlobalObj.LinkMap.remove(_clientSocket); System.out.println("客户端无响应:" + _clientSocket.socket().getPort()); } catch (Exception e) { e.printStackTrace(); } } }
这样,当客户端再次请求建立连接时,服务器端会收到之前产生异常的客户端端口发来一个读end标志,此时会通过以下代码来彻底关闭已经废弃的连接:
...
if(key.isReadable())
{
SocketChannel clientSocket = (SocketChannel)key.channel();
if(clientSocket.isOpen())
{
ByteBuffer buffer = (ByteBuffer)key.attachment();
int result = clientSocket.read(buffer);
//收到读end-stream标记
if(result == -1)
{
CloseClient(key);
}
else if(result != 0)
{
//读取数据操作
}
}
}
...
public void CloseClient(SelectionKey _key) { try { if(_key != null) { _key.cancel(); _key.channel().close(); GlobalObj.LinkMap.remove(_key.channel()); System.out.println("断开连接:"+((SocketChannel)_key.channel()).socket().getPort()); } } catch (Exception e) { e.printStackTrace(); } }
至于上述错误做法导致线程崩溃的原因,目前还不清楚,还望高人指教。
相关文章推荐
- JAVA NIO写服务端判断客户端断开连接的方法
- JAVA NIO写服务端判断客户端断开连接的方法
- socket连接 java服务器端 C#客户端进行交互 简单例子
- java中判断socket服务器端是否断开连接
- Android客户端与java服务器端的Socket连接
- Android客户端连接SSM(Spring+SpringMVC+Mybatis)框架Java服务器端
- java判断socket服务器端是否断开连接
- TCP服务器端怎么判断客户端已经关闭了连接?
- java_web当中客户端-服务器端对于中文编码格式的处理
- Java NIO SocketChannel客户端例子(支持连接失败后自动重连)
- JAVA网络编程-TCP客户端与服务器端连接
- java中判断socket服务器端是否断开连接
- java中判断socket服务器端是否断开连接
- java中判断socket服务器端是否断开连接
- android(java) socket判断网络连接状态
- java中判断socket服务器端是否断开连接
- 服务器端出现大量SYN_RECV状态,导致客户端无法连接
- java中判断socket服务器端是否断开连接
- java nio SocketChannel 服务器端与多客户端 信息交互(聊天功能)
- java在线聊天项目0.7版 连接多个客户端问题,开启多个客户端后服务器端只接收到一个 对各种异常的补充处理