网上很多关于netty重连的例子,使用何研究一段时间后发现有很大的问题:长时间重连会导致内存溢出。
所以本人经过自己的经验重新设计了这个网络,经过实际生产线1年时间的稳定运行考验,通过了7天断线重连测试。下面把这部分代码分享出来
1.pom里面添加依赖:
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.60.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
2.nio客户端连接器
package netty.client;
import java.util.concurrent.TimeUnit;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import netty.DataHandler;
import netty.Message;
/**
* nio 连接器
* @author xiaoming
*/
public class NIOConnection {
private String id; //当前客户端的id
/**
* 缓存自己
*/
private NIOConnection connection = null;
/**
*
*/
private Bootstrap b = new Bootstrap();
/**
* 当前连接通道
*/
private Channel channel = null;
/**
* 客户端线程工作组
*/
private EventLoopGroup workerGroup = new NioEventLoopGroup();
/**
* 连接器监听器
*/
private ConnectionListener connectionListener = null;
/**
* 服务端ip
*/
private String host = "127.0.0.1";
/**
* 服务端端口
*/
private int port = 6666;
/**
* 记录是否正在重连中
*/
private boolean conting = false;
/**
* 记录丢失心跳包次数,当回复通信通道后loss恢复0
*/
private int loss = 0;
/**
* 业务数据处理器,需用用户自己定位实现
*/
private DataHandler dataHandler = null;
public NIOConnection(String id, DataHandler dataHandler) {
this.id = id;
this.dataHandler = dataHandler;
}
/**
* 初次启动建立链接
* @param host
* @param port
*/
public void connect(String host, int port) {
this.host = host;
this.port = port;
/**
* 设置监听器
*/
this.connectionListener = new ConnectionListener();
/**
* 初始化 Bootstrap
*/
init();
/**
* 开始链接
*/
doConnect(null);
}
/**
* 初始化 Bootstrap
*/
private void init() {
if(this.connection == null) {
this.connection = this;
}
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 实体类传输数据,protobuf序列化
ch.pipeline().addLast("ping", new IdleStateHandler(60, 20, 60 * 10, TimeUnit.SECONDS));
ch.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
ch.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
ch.pipeline().addLast(new ClientHandler());
}
});
}
/**
* 执行连接
* @param preChannel 把上一次连接的通道销毁
*/
public synchronized void doConnect(Channel preChannel) {
if(conting || (this.channel != null && this.channel.isActive())) {
//说明连接正常 或者其他线程正在重连
return;
}
/**
* 开始重连
*/
conting = true;
//把上一个通道关闭掉
if(preChannel != null) {
try {
preChannel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
try {
ChannelFuture f = b.connect(this.host, this.port);
f.addListener(connectionListener);
channel = f.channel();
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* 向服务端发送消息
* @param text
* @return 发送成功返回true 发送成功不一定等于服务端能收到
*/
public boolean send(String text) {
boolean flag = false;
if(this.channel != null && this.channel.isActive()) {
Message message = new Message();
message.setData(text);
message.setFromId(this.id);
this.channel.writeAndFlush(message.toString());
flag = true;
}else {
//可能服务端掉线,再次重连
this.doConnect(this.channel);
}
return flag;
}
/**
* 重新连接
*/
public void reConnect() {
if(channel != null) {
channel.close();
}
conting = false;
//关掉当前通道,开启新的通道尝试连接
doConnect(channel);
}
/**
* 释放资源
*/
public void stop() {
workerGroup.shutdownGracefully();
if(this.channel != null) {
this.channel.close();
}
}
/**
* nio 通道监听器,如果连接失败则启动重连
* @author xiaoming
*/
public class ConnectionListener implements ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
//重连一次完成可能是失败的
conting = false;
if (!channelFuture.isSuccess()) {
final EventLoop loop = channelFuture.channel().eventLoop();
loop.schedule(new Runnable() {
@Override
public void run() {
System.err.println("服务端链接不上,开始重连操作...");
connection.doConnect(channelFuture.channel());
}
}, 1L, TimeUnit.SECONDS);
} else {
System.err.println("服务端链接成功...");
}
}
}
/**
* nio 客户端处理器
* @author xiaoming
*/
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
Message message = new Message((String)msg);
/**
* 解析消息是心跳包还是业务数据包
*/
if (message.getType() == Message.Type.HEART) {
//收到服务端响应的心跳包,说明通道正常 loss回归0
System.out.println("收到 heart beat");
loss = 0;
} else {
//业务消息,交给业务处理器
dataHandler.handler(message.getData());
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state().equals(IdleState.READER_IDLE)) {
System.err.println("长期没收到服务器推送数据");
//向服务器发送心跳包
/**
* loss++
* loss < 6 继续向服务发送心跳包
* loss >= 6 的直接走 重连程序
*/
loss++;
if(loss >= 6) {
reConnect();
}else {
sendHeart(ctx);
}
} else if (event.state().equals(IdleState.WRITER_IDLE)) {
System.err.println("长期未向服务器发送数据");
sendHeart(ctx);
} else if (event.state().equals(IdleState.ALL_IDLE)) {
System.err.println("ALL");
}
}
}
/**
* 发送心跳包
* @param ctx
*/
private void sendHeart(ChannelHandlerContext ctx) {
//发送心跳包
Message message = new Message();
message.setType(Message.Type.HEART);
ctx.writeAndFlush(message.toString());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.err.println("掉线了...");
//使用过程中断线重连
final EventLoop eventLoop = ctx.channel().eventLoop();
eventLoop.schedule(new Runnable() {
@Override
public void run() {
System.err.println("准备去重连...");
connection.doConnect(ctx.channel());
}
}, 1L, TimeUnit.SECONDS);
super.channelInactive(ctx);
}
}
}
3.nio 服务端
package netty.server;
import java.util.HashMap;
import java.util.Map;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import netty.DataHandler;
import netty.Message;
/**
* nio 服务端
* @author xiaoming
*/
public class NIOServer {
private String id;//服务者的id
/**
* 服务监听端口
*/
private int port = 6666;
/**
* 负责服务端任务现场组
*/
private EventLoopGroup bossGroup = new NioEventLoopGroup();
/**
* 负责客户端通道线程组
*/
private EventLoopGroup workerGroup = new NioEventLoopGroup();
/**
* 缓存客户端的通道组
*/
private Map<String, Channel> channelPool = new HashMap<String, Channel>();
/**
* 业务数据处理器,需用用户自己定位实现
*/
private DataHandler dataHandler = null;
public NIOServer(int port, String id, DataHandler dataHandler) {
this.port = port;
this.id = id;
if(dataHandler == null) {
throw new RuntimeException("DataHandler is null!");
}
this.dataHandler = dataHandler;
}
/**
* 开启NIO 服务
*/
public void start() {
bossGroup.execute(()->{
try {
//创建服务端的启动对象,设置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//设置两个线程组boosGroup和workerGroup
bootstrap.group(bossGroup, workerGroup)
//设置服务端通道实现类型
.channel(NioServerSocketChannel.class)
//设置线程队列得到连接个数
.option(ChannelOption.SO_BACKLOG, 128)
//设置保持活动连接状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
//使用匿名内部类的形式初始化通道对象
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//字符串
socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast(new ServerHandler());
}
});//给workerGroup的EventLoop对应的管道设置处理器
System.out.println("NIO Server已经开启");
//绑定端口号,启动服务端
ChannelFuture channelFuture = bootstrap.bind(this.port).sync();
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}catch (Exception e) {
e.printStackTrace();
}finally {
stop();
}
});
}
/**
* 向客户端发送文本数据
* @param text 内容
* @param toId 向那个客户端发送
* @return 发送成功返回true 发送成功不一定等于客户端能收到
*/
public boolean send(String text, String toId) {
boolean flag = false;
Channel client = channelPool.get(toId);
if(client != null) {
if(client.isActive()) {
Message message = new Message();
message.setData(text);
message.setFromId(id);
message.setToId(toId);
client.writeAndFlush(message.toString());
flag = true;
}else {
//需要销毁该通道
client.close();
channelPool.remove(toId);
}
}
return flag;
}
/**
* 释放资源
*/
public void stop() {
System.out.println("关闭服务");
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
/**
* NIO服务端处理器
* @author xiaoming
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
Message message = new Message((String)msg);
//缓存客户端的通道,用于服务端向客户端发送消息
if (channelPool.get(message.getFromId()) == null || !channelPool.get(message.getFromId()).isActive() || !channelPool.get(message.getFromId()).id().equals(ctx.channel().id())) {
channelPool.put(message.getFromId(), ctx.channel());
}
/**
* 解析消息是心跳包还是业务数据包
*/
if (message.getType() == Message.Type.HEART) {
System.out.println("server heart beat:" + 1);
// 消息id变换,并响应给客户端
message.setFromId(id);
message.setToId(message.getFromId());
ctx.writeAndFlush(message.toString());
} else {
//业务消息,交给业务处理器
dataHandler.handler(message.getData());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 当客户端通道出现问题时,关闭该通道
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.out.println("关闭通道id:" + ctx.channel().id());
ctx.close();
}
}
}
4.业务数据处理接口
package netty;
/**
* 业务数据处理接口
* @author xiaoming
*/
public interface DataHandler {
public void handler(String data);
}
5.服务端业务处理器
package netty.server;
import netty.DataHandler;
/**
* 服务端
* @author xiaoming
*/
public class ServerDataHandler implements DataHandler {
@Override
public void handler(String data) {
System.out.println("server:" + data);
}
}
6.客户端业务处理器
package netty.client;
import netty.DataHandler;
/**
* 客户端业务数据处理器
* @author xiaoming
*/
public class ClientDataHandler implements DataHandler {
@Override
public void handler(String data) {
System.out.println("client:" + data);
}
}
7.网络内部消息对象
package netty;
import com.alibaba.fastjson.JSONObject;
public class Message {
private String fromId; //发送消息者的id,记录这是谁发来的消息
private String toId; //发给目标对象的id
private String data;// 业务消息体
private Type type = Type.DATA; //默认是业务消息
public Message() {}
public Message(String str) {
Message tem = JSONObject.parseObject(str, Message.class);
this.fromId = tem.fromId;
this.toId = tem.toId;
this.data = tem.data;
this.type = tem.type;
}
public String getFromId() {
return fromId;
}
public void setFromId(String fromId) {
this.fromId = fromId;
}
public String getToId() {
return toId;
}
public void setToId(String toId) {
this.toId = toId;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public String toString(){
return JSONObject.toJSONString(this);
}
/**
* 心跳 业务数据
* @author xiaoming
*/
public enum Type{
HEART,DATA
}
}
8.启动服务
package netty.server;
public class Server {
public static void main(String[] args) {
ServerDataHandler serverDataHandler = new ServerDataHandler();
NIOServer server = new NIOServer(6666, "server_A", serverDataHandler);
server.start();
//给客户端推送消息
while(true) {
if (server.send("你好,我是服务端", "client_A")) {
System.out.println("已经发送");
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
9.启动客户端进行连接,并发送数据测试
package netty.client;
public class Client {
public static void main(String[] args) {
ClientDataHandler clientDataHandler = new ClientDataHandler();
NIOConnection connection = new NIOConnection("client_A", clientDataHandler);
connection.connect("127.0.0.1", 6666);
while(true) {
if (connection.send("我是客户端1")) {
System.out.println("已经发送");
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}