netty实现nio通信,断网重连,通道阻塞重连,商业级别代码

1 篇文章 0 订阅
1 篇文章 0 订阅

网上很多关于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();
			}
		}
		
	}

}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值