1.场景
正常的软硬件接口中(网络通信过程中),一般为了保证硬件和服务器连接一直在线,一般都是客户端主动发心跳到服务器,服务器根据收到心跳时间,判断间隔一段时间内,没有收到数据后,就断开连接。而本博客为什么要讲netty tcp空闲设置,这是因为用户很长时间不操作,则我们可以认为这个账号是僵尸账号,可以T下线,从而减少服务器的开销。而类似于王者荣耀,LOL等游戏都是这样处理的。
2.代码
package com.cloudtech.server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudtech.util.Consts;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.timeout.IdleStateHandler;
/**
*
* @ClassName: Server
* @Description:netty服务端
* @author wude
* @date 2018年9月7日
*
*/
public class Server {
protected static final Logger logger = LoggerFactory.getLogger(Server.class);
/**
* 启动
*/
public void start(int port) {
// 服务类
ServerBootstrap b = new ServerBootstrap();
// 创建boss和worker
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
//业务线程池,实现消息串行化
EventLoopGroup busyGroup = new NioEventLoopGroup();
try {
// 设置循环线程组事例
b.group(bossGroup, workerGroup);
// 设置channel工厂
b.channel(NioServerSocketChannel.class);
// 设置管道
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
//第一个参数 读超时
//第二个参数 写超时
//第三个参数读写超时
ch.pipeline().addLast(new IdleStateHandler(5, 5, 120));
ByteBuf delimiter = Unpooled.copiedBuffer("\r\n".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Consts.MAX_BUFF_LEN,delimiter));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(busyGroup,new ServerHandler());
}
});
b.option(ChannelOption.SO_BACKLOG, 2048);//serverSocketchannel的设置,链接缓冲池的大小
b.childOption(ChannelOption.SO_KEEPALIVE, true);//socketchannel的设置,维持链接的活跃,清除死链接
b.childOption(ChannelOption.TCP_NODELAY, true);//socketchannel的设置,关闭延迟发送
logger.info("启动AWS420采集成功!port={}",port);
//绑定端口
ChannelFuture future = b.bind(port);
//等待服务端关闭
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally{
//释放资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
busyGroup.shutdownGracefully();
}
}
}
注意这三个参数很重要
package com.cloudtech.server;
import java.net.InetSocketAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudtech.server.comm.ModuleId;
import com.cloudtech.server.scanner.Invoker;
import com.cloudtech.server.scanner.InvokerHoler;
import com.cloudtech.server.session.Session;
import com.cloudtech.server.session.SessionImpl;
import com.cloudtech.web.dubbo.BaseDataResult;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
*
* @ClassName: SessionManager
* @Description: 消息接受处理类
* @author wude
* @date 2018年9月7日
*/
public class ServerHandler extends SimpleChannelInboundHandler<String> {
private static Logger LOGGER = LoggerFactory.getLogger(ServerHandler.class);
@SuppressWarnings("unused")
@Override
protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {
try {
Session session = new SessionImpl(ctx.channel());
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String ip = insocket.getAddress().getHostAddress();
int port = insocket.getPort();
// 获取命令执行器
Invoker invoker = InvokerHoler.getInvoker(ModuleId.UPLOAD_DATA);
if (invoker != null) {
BaseDataResult dataResult = (BaseDataResult) invoker.invoke(session, msg, ip, port);
} else {
return;
}
} catch (Exception e) {
LOGGER.debug("AWS420 解析代码失败ip:{} port:{} result:{}", e.getMessage());
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
LOGGER.debug("进入异常方法:", cause.getMessage());
ctx.close();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
if (((IdleStateEvent) evt).state().equals(IdleState.READER_IDLE)) {
LOGGER.error("Read Idle");
ctx.close();
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
按上面的配置,5,5,120 假设客户端5秒内没有与服务器进行任何的数据交互,我们则可以认为该账号是僵尸账号,直接强制T下线