最近做的一个项目需要在服务端对连接端进行管理,故将方案记录于此。

方案实现的结果与背景

   因为服务端与客户端实现的是长连接,所以需要对客户端的连接情况进行监控,防止无效连接占用资源。

   完成类似于心跳的接收以及处理

    即:

      当连接过长事件(keep-alive Time)没有发送新的消息时,则在服务端切断其客户端的连接。

具体细节

    在处理连接(Accpet事件)时:

      将SocketChannel存入HashSet;

         以SocketChannel的HashCode作为Key来存储连接时间(以服务器时间为准)

      (开辟一个HashMap或者利用Redis进行缓存)

   在处理读取(Readable)事件时:

       以SocketChannel的HashCode作为Key来存储读取事件发生的时间(以服务器时间为准);

       处理读取事件


    开启一个定时反复运行的管理线程,每次运行对HashSet中的SocketChannel进行轮询,并以SocketChannel的HashCode去取对应的时间(LastSendTime)

    获取当前时间(CurrentTime),进行计算,如果大于Keep-Alive Time,则删除HashMap(/Redis)中的键值对,以及HashSet中的SocketChannel对象,并关闭SocketChannel。

     连接端

       ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress("127.0.0.2",1234));
serverChannel.configureBlocking(false);
AnalyisUtil util=new AnalyisUtil();
RedisConnectionPool connectionPool=new RedisConnectionPool();
Selector selector = Selector.open();
SelectionKey key = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int select = selector.select();
if (select > 0) {
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
// 接收连接请求
if (selectionKey.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) selectionKey
.channel();
SocketChannel socketChannel = channel.accept();
logger.info("接收到一个新的连接请求"+ socketChannel.getRemoteAddress().toString());
socketChannel.configureBlocking(false);
//每接收请求,注册到同一个selector中处理
socketChannel.register(selector, SelectionKey.OP_READ);
                 //在Redis中存储连接的时间,以SocketChannel的HashCode作为Key
connectionPool.getJedis().set("LST_"+socketChannel.hashCode(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                 //将SocketChannel放入HashSet中管理
connectedSokectList.add(socketChannel);
} else if (selectionKey.isReadable()) {
//执行读事件,在读事件的处理函数中,重新以SocketChannel的HashCode再次存储事件,以刷新时间
util.handleReadEvent(selectionKey,messageQueue,logger);
}
}
}
}

    连接处理线程

    

package ConnectionSystem;

import Util.RedisConnectionPool;
import org.apache.log4j.Logger;
import redis.clients.jedis.Jedis;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator; public class ConnectionManagerTask implements Runnable {
private HashSet<SocketChannel> connectedSokectList;
private long keepalive_Time=5000;
private Logger logger=Logger.getLogger(ConnectionManagerTask.class); ConnectionManagerTask(HashSet<SocketChannel> list){
logger.info("TCP监听已经启动... ...");
this.connectedSokectList=list;
} private long cucalateIsAlive(Date lastSendTime) throws ParseException {
Date currentTime=new Date();
return currentTime.getTime()-lastSendTime.getTime();
} private boolean handleSocket(SocketChannel channel){
int channel_code= channel.hashCode();
RedisConnectionPool connectionPool=new RedisConnectionPool();
Jedis jedisCilent;
SocketAddress ipLocation;
try{
ipLocation=channel.getRemoteAddress();
jedisCilent=connectionPool.getJedis();
String SendTime=jedisCilent.get("LST_"+channel_code);
if(SendTime!=null) {
SimpleDateFormat dfs = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date lastSendTime = dfs.parse(SendTime);
if (cucalateIsAlive(lastSendTime) > keepalive_Time) {
//超过时间
try {
if(channel.isConnected()){
channel.close();
jedisCilent.del("LST_"+channel_code);
logger.debug("连接被TCP管理线程关闭,ip:" + ipLocation + ",上次回应时间:" + lastSendTime);
}else {
logger.debug("当前通道,ip:" + ipLocation + "已经关闭... ..."+ ",上次回应时间:" + lastSendTime);
}
return true;
} catch (IOException e) {
logger.error("通道,ip:" + ipLocation + "关闭时发生了异常",e);
}
}else {
return false;
}
}
if(channel.isConnected()){
channel.close();
logger.debug("连接被TCP管理线程关闭,ip:" + ipLocation + ":未检测到登陆时间... ...");
}else {
logger.debug("当前通道,ip:" + ipLocation + "已经关闭... ...");
} }catch (Exception e){
logger.error("通道关闭时发生了异常",e);
}
return true;
} @Override
public void run() {
logger.info("当前连接数"+connectedSokectList.size());
if(connectedSokectList.isEmpty()){
return;
}
Iterator<SocketChannel> iterator = connectedSokectList.iterator();
while (iterator.hasNext()){
SocketChannel socketChannel=iterator.next();
Boolean removeFlag=handleSocket(socketChannel);
if(removeFlag){
iterator.remove();
}
}
}
}

   

NIO 服务端TCP连接管理的方案的更多相关文章

  1. 经典!服务端 TCP 连接的 TIME_WAIT 过多问题的分析与解决

    开源Linux 专注分享开源技术知识 本文给出一个 TIME_WAIT 状态的 TCP 连接过多的问题的解决思路,非常典型,大家可以好好看看,以后遇到这个问题就不会束手无策了. 问题描述 模拟高并发的 ...

  2. 服务端 TCP 连接的 TIME_WAIT 过多问题的分析与解决

    https://mp.weixin.qq.com/s/VRQ_12tzy3gRYD091cI7Ew

  3. Kafka技术内幕 读书笔记之(二) 生产者——服务端网络连接

    KafkaServer是Kafka服务端的主类, KafkaServer中和网络层有关的服务组件包括 SocketServer.KafkaApis 和 KafkaRequestHandlerPool后 ...

  4. NIO服务端主要创建过程

    NIO服务端主要创建过程:   步骤一:打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的副管道,示例代码如下:      ServerSocketChannel ...

  5. TCP系列07—连接管理—6、TCP连接管理的状态机

            经过前面对TCP连接管理的介绍,我们本小节通过TCP连接管理的状态机来总结一下看看TCP连接的状态变化 一.TCP状态机整体状态转换图(截取自第二版TCPIP详解) 二.TCP连接建立 ...

  6. 6-1 建立客户端与zk服务端的连接

    6-1 建立客户端与zk服务端的连接 zookeeper原生java api使用 会话连接与恢复; 节点的增删改查; watch与acl的相关操作; 导入jar包;

  7. TCP连接管理(TCP Connection Management)

    在最近的求职面试过程中,关于"建立TCP连接的三次握手"不止一次被问到了,虽然我以前用同样的问题面试过别人,但感觉还是不能给面试官一个很清晰的回答.本文算是对整个TCP连接管理做一 ...

  8. 客户端 new socket时候 就像服务端发起连接了

    客户端 new socket时候  就像服务端发起连接了

  9. 客户端(springmvc)调用netty构建的nio服务端,获得响应后返回页面(同步响应)

    后面考虑通过netty做一个真正意义的简约版RPC框架,今天先尝试通过正常调用逻辑调用netty构建的nio服务端并同步获得返回信息.为后面做铺垫 服务端实现 我们先完成服务端的逻辑,逻辑很简单,把客 ...

随机推荐

  1. 【读书笔记 - Effective Java】01. 考虑用静态工厂方法代替构造器

    获取类的实例有两种方法: 1. 提供一个公有的构造器(最常用). 2. 提供一个公有的静态工厂方法(static factory method). // 静态工厂方法示例 public static ...

  2. 关于mybatis编写sql问题.

    最近呢楼主回到长沙进行面试:被问了一个这样的问题,在mybatis中怎么进行模糊查询,望各位大佬在下方进行评论,好让我这菜鸡多学习一些.

  3. react路由配置(未完)

    React路由 React推了两个版本 一个是react-router 一个是react-router-dom 个人建议使用第二个 因为他多了一个Link组件 Npm install react-ro ...

  4. Redis(三):Redis数据类型

    Redis数据类型目录导航: Redis五大数据类型 哪里去获取Redis常见数据类型操作命令 Redis键(Key) Redis字符串(String) Redis列表(List) Redis集合(S ...

  5. xshell安装教程

    Xshell安装使用教程 Xshell 是一个强大的安全终端模拟软件,它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议.Xshell 通过互联网到远程主机 ...

  6. Ruby中类的进阶(继承,private, public, protect)

    类中的public,protect,private public method class Point def test end end 这样定义的test方法就是一个public方法可以在类内外使用 ...

  7. 用elk+filebeat监控容器日志

    elk  为 elasticsearch(查询搜索引擎),logstash(对日志进行分析和过滤,然后转发给elasticsearch),kibana(一个web图形界面用于可视化elasticsea ...

  8. Docker开篇之基础概念篇

    What--什么是容器? 容器技术,是一种操作系统层的虚拟化(Operating system-level virtualization),它将应用软件系统打包成一个软件容器(Container),内 ...

  9. CSS 兼容iPhone X、iPhone XS及iPhone XR

    @media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ra ...

  10. 滑雪_KEY

    滑雪 ( skiing.pas/c/cpp) [题目描述] MM 参加一个滑雪比赛,滑雪场是一个 N×M 的矩形, MM 要从起点( 1, 1)滑到( N,M).矩形中每个单位格子有一个海拔高度值 h ...