服务端
public class Netty5Server {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
// 首先,netty通过ServerBootstrap启动服务端
ServerBootstrap server = new ServerBootstrap();
/**
* 第1步定义两个线程组,用来处理客户端通道的accept和读写事件
* bossGroup用来处理accept事件,workergroup用来处理通道的读写事件
* bossGroup获取客户端连接,连接接收到之后再将连接转发给workergroup去处理
*/
server.group(bossGroup, workerGroup)
//第2步 构造Socketchannel的工厂类,绑定服务端通道
.channel(NioServerSocketChannel.class)
//用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
//用来初始化服务端可连接队列
//服务端处理客户端连接请求是按顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
.option(ChannelOption.SO_BACKLOG, 1024)
//第3步绑定handler,处理读写事件,ChannelInitializer是给通道初始化
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//空闲处理程序
ch.pipeline().addLast(new IdleStateHandler(3,0,0, TimeUnit.SECONDS));
ch.pipeline().addLast("decoder",new StringDecoder());
ch.pipeline().addLast("encoder",new StringEncoder());
//处理服务端的业务逻辑:心跳超时处理、客服端返回的数据处理
ch.pipeline().addLast(new Test5ServerHandler());
}
});
//第4步绑定8080端口
ChannelFuture channelFuture = server.bind(8005).sync();
System.out.println("server start.");
//当通道关闭了,就继续往下走
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class Test5ServerHandler extends ChannelInboundHandlerAdapter {
private int idle_count = 1;//空闲次数
private int count = 1;//发送次数
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception{ //建立连接时
System.out.println("[Server]获得连接:"+ctx.channel().remoteAddress() + date());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("[Server] the "+count+" times, recive msg: " + msg);
String message = (String)msg;
System.out.println("[Server] response msg: "+message);
// 如果是心跳命令,服务端收到命令后回复一个相同的命令给客户端
if("hb_request".equals(message)){
ctx.channel().writeAndFlush("server is active.");
}else{
ctx.channel().writeAndFlush("解析数据");
}
count++;
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { //用户超时事件触发
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent)evt;
String eventType = null;
switch (event.state()){
case READER_IDLE: // 如果读通道处于空闲状态,说明没有接收到心跳命令
eventType = "读空闲";
idle_count ++;
break;
case WRITER_IDLE:
eventType = "写空闲";
break;//不处理
case ALL_IDLE:
eventType = "读写空闲";//不处理
break;
}
System.out.println(ctx.channel().remoteAddress() + "userEventTriggered()读到超时事件:"+event.state()+" "+eventType);
if(idle_count > 5){
System.out.println("[Server]超过3次无客户端请求(读空闲超过3次),关闭该channel");
ctx.channel().writeAndFlush("you are out");
//ctx.channel().close();
}else
super.userEventTriggered(ctx, evt); //ctx.fireUserEventTriggered(evt);
}
}
private String date(){
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-DD HH:mm:ss");
return " "+ sdf.format(new Date());
}
}
客户端、
public class Netty5Client {
public static void main(String[] args) throws Exception {
// 首先,netty通过Bootstrap启动客户端
Bootstrap bootstrap = new Bootstrap();
// 第1步 定义线程组,处理读写和链接事件,没有了accept事件
EventLoopGroup worker = new NioEventLoopGroup();
bootstrap.group(worker)
// 第2步 构造Socketchannel的工厂类,绑定客户端通道
.channel(NioSocketChannel.class)
// 第3步 给NioSocketChannel初始化handler, 处理读写事件
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//空闲处理程序
ch.pipeline().addLast(new IdleStateHandler(0,2,0, TimeUnit.SECONDS));
// 解码和编码,应和服务端一致
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
//客户端业务处理,如编解码和心跳的设置
ch.pipeline().addLast(new Test5ClientHandler());
}
});
// 连接服务端
Channel channel = bootstrap.connect("127.0.0.1",8005).sync().channel();//connect server
String text = "CLIENT xxx START ONLINE.";
channel.writeAndFlush(text);//给服务端发送数据
System.out.println("[Client] start send msg to server: " + text);
channel.closeFuture().sync();
}
}
客户端业务处理类
public class Test5ClientHandler extends ChannelInboundHandlerAdapter {
/** 客户端请求的心跳命令 */
private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("hb_request", CharsetUtil.UTF_8));
private int idle_count = 1;//idle count
private int count = 1;//send count
private int cycleTimes = 1;//循环次数
//超时处理方法
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object obj){
if(obj instanceof IdleStateEvent){
IdleStateEvent event = (IdleStateEvent)obj;
if(IdleState.WRITER_IDLE.equals(event.state())){ //如果写通道处于空闲就发送心跳命令
//设置发送次数,允许发送3次心跳包
if(idle_count <= 10){
idle_count++;
ctx.channel().writeAndFlush(HEARTBEAT_SEQUENCE.duplicate());
}else{
System.out.println("停止发送心跳请求...");
}
}
}
cycleTimes++;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception{
System.out.println("[Client]conn req :"+ date());
ctx.channel().writeAndFlush("this client activing...");
ctx.fireChannelActive();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("[Client]conn sucess :"+count+",Read msg : "+ msg);//业务逻辑处理
count++;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception{
System.out.println("[Client]conn close :"+date());
}
private String date(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(new Date());
}
}
参考资料
Netty中ctx.writeAndFlush与ctx.channel().writeAndFlush的区别
https://blog.csdn.net/FishSeeker/article/details/78447684
https://blog.csdn.net/chehec2010/article/details/90643436
网络编程Netty IoT长连接优化 https://blog.csdn.net/qq_34365173/article/details/106311934
断线重连机制实现 https://blog.csdn.net/li_c_yang/article/details/104841022
Netty检查连接断开的几种方法 https://www.cnblogs.com/alan6/p/11715722.html