NIO 服务端TCP连接管理的方案
最近做的一个项目需要在服务端对连接端进行管理,故将方案记录于此。
方案实现的结果与背景
因为服务端与客户端实现的是长连接,所以需要对客户端的连接情况进行监控,防止无效连接占用资源。
完成类似于心跳的接收以及处理
即:
当连接过长事件(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连接管理的方案的更多相关文章
- 经典!服务端 TCP 连接的 TIME_WAIT 过多问题的分析与解决
开源Linux 专注分享开源技术知识 本文给出一个 TIME_WAIT 状态的 TCP 连接过多的问题的解决思路,非常典型,大家可以好好看看,以后遇到这个问题就不会束手无策了. 问题描述 模拟高并发的 ...
- 服务端 TCP 连接的 TIME_WAIT 过多问题的分析与解决
https://mp.weixin.qq.com/s/VRQ_12tzy3gRYD091cI7Ew
- Kafka技术内幕 读书笔记之(二) 生产者——服务端网络连接
KafkaServer是Kafka服务端的主类, KafkaServer中和网络层有关的服务组件包括 SocketServer.KafkaApis 和 KafkaRequestHandlerPool后 ...
- NIO服务端主要创建过程
NIO服务端主要创建过程: 步骤一:打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的副管道,示例代码如下: ServerSocketChannel ...
- TCP系列07—连接管理—6、TCP连接管理的状态机
经过前面对TCP连接管理的介绍,我们本小节通过TCP连接管理的状态机来总结一下看看TCP连接的状态变化 一.TCP状态机整体状态转换图(截取自第二版TCPIP详解) 二.TCP连接建立 ...
- 6-1 建立客户端与zk服务端的连接
6-1 建立客户端与zk服务端的连接 zookeeper原生java api使用 会话连接与恢复; 节点的增删改查; watch与acl的相关操作; 导入jar包;
- TCP连接管理(TCP Connection Management)
在最近的求职面试过程中,关于"建立TCP连接的三次握手"不止一次被问到了,虽然我以前用同样的问题面试过别人,但感觉还是不能给面试官一个很清晰的回答.本文算是对整个TCP连接管理做一 ...
- 客户端 new socket时候 就像服务端发起连接了
客户端 new socket时候 就像服务端发起连接了
- 客户端(springmvc)调用netty构建的nio服务端,获得响应后返回页面(同步响应)
后面考虑通过netty做一个真正意义的简约版RPC框架,今天先尝试通过正常调用逻辑调用netty构建的nio服务端并同步获得返回信息.为后面做铺垫 服务端实现 我们先完成服务端的逻辑,逻辑很简单,把客 ...
随机推荐
- tctip打赏小插件
tctip是一个js插件,作用是在web网页右侧生成一个打赏浮动窗 使用方法 页面使用(多数人的使用方式) 插件下载地址 第一步,引入js 一般引入min版本,即引入tctip-版本号.min.js文 ...
- Linux磁盘管理和lvm
磁盘管理 硬盘接口和硬盘种类 从整体的角度上,硬盘接口分为IDE.SATA.SCSI和SAS四种,IDE接口硬盘多用于家用产品中,也部分应用于服务器,SCSI接口的硬盘则主要应用于服务器市场,而SAS ...
- 【memcached启动报错】
#前台启动不了 #指定-u root #后台启动 #扩展选项: #利用telnet连接memcached 的端口登录memcached服务 #error表示有语法错误 #store表示正确
- Java Integer == 以及分析
Java Integer == 先看一下这段代码 Integer integer1 = 100; Integer integer2 = 100; System.out.println("in ...
- vim 粘贴文本,格式混乱 tab
粘贴的代码如上.修改方法: 方法一: set paste 贴完后,设置 set nopaste 恢复代码缩进. 方法二:修改配置文件 vim /etc/vim/vimrc set pastetoggl ...
- [NOIP2017]逛公园(DP)
先spfa一遍处理出d[]数组,(从n开始bfs一遍标记可以达到n的点) 题意即,在走最短路的基础上,可以最多多走K长度的路径, 考虑DP,每次剩余可走的长度会因决策而改变,所以考虑dp[i][j]为 ...
- 【SQLSERVER】从数据库文件mdf中拆分ndf的方法和利弊
一.数据文件格式 SQLSERVER中,数据库的文件后缀有3种:mdf.ndf.ldf. 如下图所示,DW_TEST.mdf.DW_TEST_HIS.ndf.DW_TEST.ldf 属于同一个数据库T ...
- (转) 如何从 0 开始学 ruby on rails (漫步版)
原文:http://readful.com/post/12322300571/0-ruby-on-rails ruby 是一门编程语言,ruby on rails 是 ruby 的一个 web 框架, ...
- HashMap在并发场景下踩过的坑
本文来自网易云社区 作者:张伟 关于HashMap在并发场景下的问题有很多人,很多公司遇到过!也很多人总结过,我们很多时候都认为这样都坑距离自己很远,自己一定不会掉入这样都坑.可是我们随时都有就遇到了 ...
- 对JSON的理解
JSON语法: JSON是一种结构化数据,它是一种数据格式 JSON可以概括为三种类型:简单值.对象.数组 注意:JSON不支持变量.函数和对象实例 一.JSON简单值 包括字符串.数值.布尔值.和n ...