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

方案实现的结果与背景

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

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

    即:

      当连接过长事件(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();
}
}
}
}

   

最新文章

  1. 【CISP笔记】安全漏洞与恶意代码(2)
  2. 2016年12月17日 星期六 --出埃及记 Exodus 21:12
  3. 浏览器对象模型BOM
  4. 【BZOJ-1984】月下“毛景树” 树链剖分
  5. man page的介绍
  6. 日期转换类 DateConverter.java
  7. JDBC连接Oracle数据库的问题
  8. Request.IsLocal与Request.Url.IsLoopback的区别
  9. Arcgis Engine最短路径分析
  10. [RxJS + AngularJS] Sync Requests with RxJS and Angular
  11. 【C#编程基础学习笔记】6---变量的命名
  12. [WPF疑难]避免窗口最大化时遮盖任务栏
  13. arcpy.mapping常用四大件-Layer
  14. 我的前端故事----我为什么用GraphQL
  15. java 多态 ---父类调用子类方法
  16. OpenCV提取显示一张图片(或者视频)的R,G,B颜色分量
  17. locate命令
  18. Lodop打印控件 打印‘接下一页’‘以下空白’
  19. Android OpenGL ES 开发(N): OpenGL ES 2.0 机型兼容问题整理
  20. 利用ConcurrentHashMap来实现一个ConcurrentHashSet

热门文章

  1. echarts 多图任意布局案例
  2. 前端js转换时间戳为时间类型显示
  3. font(字体)所使用的属性
  4. Java : java基础(5) Socket网络编程
  5. STM32(1)——使用Keil MDK以及标准外设库创建STM32工程
  6. centos7下使用n grok编译服务端和客户端穿透内网
  7. Python学习手册之 Python 之禅、Python 编程规范和函数参数
  8. vuejs中的生命周期
  9. 使用idea上传项目到gitHub
  10. DATA 转 16 进制