12.netty开发私有协议栈
来源:互联网 发布:oa系统的数据库设计 编辑:程序博客网 时间:2024/05/17 03:24
1.介绍
通信协议也可以企业定制。绝大多数私有协议(非标准协议)在传输层都是使用TCP,所以使用netty的TCP协议栈可以方便的进行私有协议栈的开发。常见的公有协议就是如http这一类的。实际上私有协议并没有标准的定义,只要是能用于跨节点、跨主机、跨进程通信都可以成为私有协议。
java传统跨节点通信方式:
通过rmi远程调用
socket+java序列化远程调用
开源rpc框架(Facebook的Thrift、Apache的Avro)
公有协议远程调用(http+xml、restful+json)
远程调用细节:出去链路层的物理连接外,首先要对请求和响应的消息进行编码解码,消息需要携带一些其他数据例如控制管理指令例如握手标志或者心跳检测的参数等等。当这些动能组合到一起之后就形成了私有协议
2.netty协议
目标是使用netty开发一个私有协议用于节点通信,基于TCP/IP协议栈。类似于http的一种应用层协议这里称为netty协议。
netty协议功能描述:基于netty的nio实现高性能异步通信、提供消息编码解码同时实现pojo序列化和凡序列化、提供基于ip地址的白名单接入认证、链路心跳检测、链路断线重连
netty协议通信模型:
netty协议消息定义:
消息头:
1.crcCode(校验码)2.length(消息总长度)3.sessionID(各节点共享)4.type(消息类型:0业务请求/1业务响应/2业务请求和响应信息/3握手请求/4握手应答/5心跳请求/6心跳应答)5.priority(消息优先级)attachment(可选消息用于扩展消息头)
netty协议编码:
netty协议解码:
netty协议链路建立:
netty协议心跳机制:
netty协议重连机制:
netty协议禁止重复登录:
netty协议消息缓存重发:
netty协议请求地址ip合法性检查:
3.netty服务端/客户端
依赖
<!-- netty依赖all --><dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha1</version></dependency><!-- marshalling --><dependency> <groupId>org.jboss.marshalling</groupId> <artifactId>jboss-marshalling</artifactId> <version>1.3.0.GA</version> </dependency> <dependency> <groupId>org.jboss.marshalling</groupId> <artifactId>jboss-marshalling-serial</artifactId> <version>1.3.0.GA</version> <scope>test</scope> </dependency>
(1)消息
消息定义
package com.tyf.message;/* * 消息定义: * * */public final class NettyMessage {//消息头 private Header header; //消息体使用对象序列化传输 private Object body; public final Header getHeader() {return header; } public final void setHeader(Header header) {this.header = header; } public final Object getBody() {return body; } public final void setBody(Object body) {this.body = body; } @Override public String toString() {return "NettyMessage [header=" + header + "]"; }}
消息头
package com.tyf.message;import java.util.HashMap;import java.util.Map;/* * * 消息头数据结构定义 * * */public final class Header { private int crcCode = 0xabef0101; private int length;// 消息长度 private long sessionID;// 会话ID private byte type;// 消息类型 private byte priority;// 消息优先级 private Map<String, Object> attachment = new HashMap<String, Object>(); // 附件(可选字段) /** * @return the crcCode */ public final int getCrcCode() {return crcCode; } /** * @param crcCode * the crcCode to set */ public final void setCrcCode(int crcCode) {this.crcCode = crcCode; } /** * @return the length */ public final int getLength() {return length; } /** * @param length * the length to set */ public final void setLength(int length) {this.length = length; } /** * @return the sessionID */ public final long getSessionID() {return sessionID; } /** * @param sessionID * the sessionID to set */ public final void setSessionID(long sessionID) {this.sessionID = sessionID; } /** * @return the type */ public final byte getType() {return type; } /** * @param type * the type to set */ public final void setType(byte type) {this.type = type; } /** * @return the priority */ public final byte getPriority() {return priority; } /** * @param priority * the priority to set */ public final void setPriority(byte priority) {this.priority = priority; } /** * @return the attachment */ public final Map<String, Object> getAttachment() {return attachment; } /** * @param attachment * the attachment to set */ public final void setAttachment(Map<String, Object> attachment) {this.attachment = attachment; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() {return "Header [crcCode=" + crcCode + ", length=" + length+ ", sessionID=" + sessionID + ", type=" + type + ", priority="+ priority + ", attachment=" + attachment + "]"; }}
消息种类
package com.tyf.message;/* * 定义请求的类型 * 0:业务请求信息 * 1:业务响应信息 * 2:业务请求响应信息 * 3:握手请求信息 * 4:握手应答信息 * 5:心跳请求信息 * 6:心跳应答信息 * */public enum MessageType { SERVICE_REQ((byte) 0), SERVICE_RESP((byte) 1), ONE_WAY((byte) 2), LOGIN_REQ( (byte) 3), LOGIN_RESP((byte) 4), HEARTBEAT_REQ((byte) 5), HEARTBEAT_RESP( (byte) 6); private byte value; private MessageType(byte value) {this.value = value; } public byte value() {return this.value; }}
(2)编码解码器
序列化输入流
package com.tyf.coder;import io.netty.buffer.ByteBuf;import org.jboss.marshalling.ByteInput;import java.io.IOException;/* * 通道内缓冲区的数据输入流,封装marshalling.ByteInput * * */class ChannelBufferByteInput implements ByteInput { private final ByteBuf buffer; public ChannelBufferByteInput(ByteBuf buffer) { this.buffer = buffer; } public void close() throws IOException { // nothing to do } public int available() throws IOException { return buffer.readableBytes(); } public int read() throws IOException { if (buffer.isReadable()) { return buffer.readByte() & 0xff; } return -1; } public int read(byte[] array) throws IOException { return read(array, 0, array.length); } public int read(byte[] dst, int dstIndex, int length) throws IOException { int available = available(); if (available == 0) { return -1; } length = Math.min(available, length); buffer.readBytes(dst, dstIndex, length); return length; } public long skip(long bytes) throws IOException { int readable = buffer.readableBytes(); if (readable < bytes) { bytes = readable; } buffer.readerIndex((int) (buffer.readerIndex() + bytes)); return bytes; }}
序列化输出流
package com.tyf.coder;import io.netty.buffer.ByteBuf;import org.jboss.marshalling.ByteOutput;import java.io.IOException;/* * 通道内缓冲区的数据输入流,封装marshalling.ByteOutput * * */class ChannelBufferByteOutput implements ByteOutput { private final ByteBuf buffer; public ChannelBufferByteOutput(ByteBuf buffer) { this.buffer = buffer; } public void close() throws IOException { // Nothing to do } public void flush() throws IOException { // nothing to do } public void write(int b) throws IOException { buffer.writeByte(b); } public void write(byte[] bytes) throws IOException { buffer.writeBytes(bytes); } public void write(byte[] bytes, int srcIndex, int length) throws IOException { buffer.writeBytes(bytes, srcIndex, length); } ByteBuf getBuffer() { return buffer; }}
编码工厂类
package com.tyf.coder;import java.io.IOException;import org.jboss.marshalling.Marshaller;import org.jboss.marshalling.MarshallerFactory;import org.jboss.marshalling.Marshalling;import org.jboss.marshalling.MarshallingConfiguration;import org.jboss.marshalling.Unmarshaller;/* * marshalling序列化编解码器工厂 * * */public final class MarshallingCodecFactory {//Marshaller:静态序列化构建器 protected static Marshaller buildMarshalling() throws IOException { final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");final MarshallingConfiguration configuration = new MarshallingConfiguration();configuration.setVersion(5);Marshaller marshaller = marshallerFactory.createMarshaller(configuration);return marshaller; } //Unmarshalle:静态r反序列化构建器 protected static Unmarshaller buildUnMarshalling() throws IOException {final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");final MarshallingConfiguration configuration = new MarshallingConfiguration();configuration.setVersion(5);final Unmarshaller unmarshaller = marshallerFactory.createUnmarshaller(configuration);return unmarshaller; }}
消息体编码器
package com.tyf.coder;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandler.Sharable;import java.io.IOException;import org.jboss.marshalling.Marshaller;/* * 消息体编码工具类 * * */@Sharablepublic class MarshallingEncoder { private static final byte[] LENGTH_PLACEHOLDER = new byte[4]; Marshaller marshaller; //通过解码工厂获取编码构建器,通过构建器获取编码器 public MarshallingEncoder() throws IOException {marshaller = MarshallingCodecFactory.buildMarshalling(); } //编码(对象序列化为byte数组) protected void encode(Object msg, ByteBuf out) throws Exception {try { int lengthPos = out.writerIndex(); out.writeBytes(LENGTH_PLACEHOLDER); ChannelBufferByteOutput output = new ChannelBufferByteOutput(out); marshaller.start(output); marshaller.writeObject(msg); marshaller.finish(); out.setInt(lengthPos, out.writerIndex() - lengthPos - 4);} finally { marshaller.close();} }}
消息体解码器
package com.tyf.coder;import io.netty.buffer.ByteBuf;import java.io.IOException;import java.io.StreamCorruptedException;import org.jboss.marshalling.ByteInput;import org.jboss.marshalling.Unmarshaller;/* * * 消息体解码工具类 * */public class MarshallingDecoder { private final Unmarshaller unmarshaller; //编码工厂获取构建器,构建器获取解码器 public MarshallingDecoder() throws IOException {unmarshaller = MarshallingCodecFactory.buildUnMarshalling(); } //解码(反序列化) protected Object decode(ByteBuf in) throws Exception {int objectSize = in.readInt();ByteBuf buf = in.slice(in.readerIndex(), objectSize);//调用自定义缓冲区数据输入流获取数据ByteInput input = new ChannelBufferByteInput(buf);try { unmarshaller.start(input); Object obj = unmarshaller.readObject(); unmarshaller.finish(); in.readerIndex(in.readerIndex() + objectSize); //返回消息对象 return obj;} finally { unmarshaller.close();} }}
消息编码器
package com.tyf.coder;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.MessageToByteEncoder;import io.netty.channel.ChannelHandler.Sharable;import java.io.IOException;import java.util.Map;import com.tyf.message.NettyMessage;/* * 消息编码类 * * */@Sharablepublic final class NettyMessageEncoder extends MessageToByteEncoder<NettyMessage> {//消息体编码器(序列化) MarshallingEncoder marshallingEncoder; public NettyMessageEncoder() throws IOException {this.marshallingEncoder = new MarshallingEncoder(); } //编码方法 @Override protected void encode(ChannelHandlerContext ctx, NettyMessage msg, ByteBuf sendBuf) throws Exception {if (msg == null || msg.getHeader() == null) throw new Exception("The encode message is null");//获取消息头,直接用ByteBuf各个方法编码并写入缓冲sendBuf.writeInt((msg.getHeader().getCrcCode()));sendBuf.writeInt((msg.getHeader().getLength()));sendBuf.writeLong((msg.getHeader().getSessionID()));sendBuf.writeByte((msg.getHeader().getType()));sendBuf.writeByte((msg.getHeader().getPriority()));sendBuf.writeInt((msg.getHeader().getAttachment().size()));String key = null;byte[] keyArray = null;Object value = null;for (Map.Entry<String, Object> param : msg.getHeader().getAttachment().entrySet()) { key = param.getKey(); keyArray = key.getBytes("UTF-8"); sendBuf.writeInt(keyArray.length); sendBuf.writeBytes(keyArray); value = param.getValue(); //消息头的可选参数也是用消息体的编码器 marshallingEncoder.encode(value, sendBuf);}key = null;keyArray = null;value = null;if (msg.getBody() != null) {//将消息体编码 marshallingEncoder.encode(msg.getBody(), sendBuf);} else sendBuf.writeInt(0);sendBuf.setInt(4, sendBuf.readableBytes() - 8); }}
消息解码器
package com.tyf.coder;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.LengthFieldBasedFrameDecoder;import io.netty.channel.ChannelHandler.Sharable;import java.io.IOException;import java.util.HashMap;import java.util.Map;import com.tyf.message.Header;import com.tyf.message.NettyMessage;/* * 消息解码器(解码消息头、解码消息体) * * */@Sharablepublic class NettyMessageDecoder extends LengthFieldBasedFrameDecoder {//消息体解码器 MarshallingDecoder marshallingDecoder; public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) throws IOException {super(maxFrameLength, lengthFieldOffset, lengthFieldLength);marshallingDecoder = new MarshallingDecoder(); } //消息解码方法 @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { //父类decode方法将缓冲区数据解码成ByteBufByteBuf frame = (ByteBuf) super.decode(ctx, in);if (frame == null) { return null;}//通过父类解码后的消息直接构造消息头NettyMessage message = new NettyMessage();Header header = new Header();header.setCrcCode(frame.readInt());header.setLength(frame.readInt());header.setSessionID(frame.readLong());header.setType(frame.readByte());header.setPriority(frame.readByte());int size = frame.readInt();if (size > 0) { Map<String, Object> attch = new HashMap<String, Object>(size); int keySize = 0; byte[] keyArray = null; String key = null; for (int i = 0; i < size; i++) {keySize = frame.readInt();keyArray = new byte[keySize];frame.readBytes(keyArray);key = new String(keyArray, "UTF-8");attch.put(key, marshallingDecoder.decode(frame)); } keyArray = null; key = null; header.setAttachment(attch);}if (frame.readableBytes() > 4) {//消息体序列化成对象,直接构造消息体 message.setBody(marshallingDecoder.decode(frame));}message.setHeader(header);//返回解码后的消息return message; }}
(3)登录认证
认证请求handler
package com.tyf.loginAuth;import io.netty.channel.ChannelHandlerAdapter;import io.netty.channel.ChannelHandlerContext;import java.net.InetSocketAddress;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import com.tyf.message.Header;import com.tyf.message.MessageType;import com.tyf.message.NettyMessage;/* * 客户端请求安全认证 * 0:业务请求信息 * 1:业务响应信息 * 2:业务请求响应信息 * 3:握手请求信息 * 4:握手应答信息 * 5:心跳请求信息 * 6:心跳应答信息 * * */public class LoginAuthRespHandler extends ChannelHandlerAdapter { private Map<String, Boolean> nodeCheck = new ConcurrentHashMap<String, Boolean>(); //客户端地址白名单(白名单之外的请求禁止访问) private String[] whitekList = { "127.0.0.1", "192.168.1.104" }; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {NettyMessage message = (NettyMessage) msg;// 如果是握手请求消息,处理,其它消息透传if (message.getHeader() != null&& message.getHeader().getType() == MessageType.LOGIN_REQ.value()) { String nodeIndex = ctx.channel().remoteAddress().toString(); NettyMessage loginResp = null; // 重复登陆,拒绝 if (nodeCheck.containsKey(nodeIndex)) {loginResp = buildResponse((byte) -1); } else {InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();String ip = address.getAddress().getHostAddress();boolean isOK = false;for (String WIP : whitekList) { if (WIP.equals(ip)) {isOK = true;break; }}//消息回传(buildResponse((byte) -1)构造握手确认的回传信息)loginResp = isOK ? buildResponse((byte) 0): buildResponse((byte) -1);if (isOK) nodeCheck.put(nodeIndex, true); } System.out.println("The login response is : " + loginResp + " body [" + loginResp.getBody() + "]"); ctx.writeAndFlush(loginResp);} else { ctx.fireChannelRead(msg);} } private NettyMessage buildResponse(byte result) {NettyMessage message = new NettyMessage();Header header = new Header();header.setType(MessageType.LOGIN_RESP.value());message.setHeader(header);message.setBody(result);return message; } public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();nodeCheck.remove(ctx.channel().remoteAddress().toString());// 删除缓存ctx.close();ctx.fireExceptionCaught(cause); }}
(4)心跳检测
心跳请求handler
package com.tyf.heartBeat;import com.tyf.message.Header;import com.tyf.message.MessageType;import com.tyf.message.NettyMessage;import io.netty.channel.ChannelHandlerAdapter;import io.netty.channel.ChannelHandlerContext;/* * 心跳应答的handler:获取client发送的心跳信息(心跳信息比较简单不含消息体) * */public class HeartBeatRespHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {NettyMessage message = (NettyMessage) msg;// 返回心跳应答消息if (message.getHeader() != null&& message.getHeader().getType() == MessageType.HEARTBEAT_REQ.value()) { System.out.println("Receive client heart beat message : ---> " + message); NettyMessage heartBeat = buildHeatBeat(); System.out .println("Send heart beat response message to client : ---> " + heartBeat); ctx.writeAndFlush(heartBeat);} else ctx.fireChannelRead(msg); } //穿件一个心跳应答的message信息回传 private NettyMessage buildHeatBeat() {NettyMessage message = new NettyMessage();Header header = new Header();header.setType(MessageType.HEARTBEAT_RESP.value());message.setHeader(header);return message; }}
(5)server
地址信息
package com.tyf.server;/* * * 记录server端地址和端口信息 * */public final class NettyConstant { public static final String REMOTEIP = "127.0.0.1"; public static final int PORT = 8080; public static final int LOCAL_PORT = 12088; public static final String LOCALIP = "127.0.0.1";}
启动类
package com.tyf.server;import io.netty.bootstrap.ServerBootstrap;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.logging.LogLevel;import io.netty.handler.logging.LoggingHandler;import io.netty.handler.timeout.ReadTimeoutHandler;import java.io.IOException;import com.tyf.coder.NettyMessageDecoder;import com.tyf.coder.NettyMessageEncoder;import com.tyf.heartBeat.HeartBeatRespHandler;import com.tyf.loginAuth.LoginAuthRespHandler;/* * * server起步程序 * * */public class NettyServer { public void bind() throws Exception {// 配置服务端的NIO线程组EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws IOException {ch.pipeline().addLast(new NettyMessageDecoder(1024 * 1024, 4, 4));//消息解码ch.pipeline().addLast(new NettyMessageEncoder());//消息编码ch.pipeline().addLast("readTimeoutHandler",new ReadTimeoutHandler(50));//心跳超时,一段时间未收到client消息时主动关闭链路ch.pipeline().addLast(new LoginAuthRespHandler());//访问白名单handlerch.pipeline().addLast("HeartBeatHandler",new HeartBeatRespHandler());//心跳检测handler }});// 绑定端口,同步等待成功b.bind(NettyConstant.REMOTEIP, NettyConstant.PORT).sync();System.out.println("Netty server start ok : "+ (NettyConstant.REMOTEIP + " : " + NettyConstant.PORT)); } public static void main(String[] args) throws Exception {new NettyServer().bind(); }}
4.nettr客户端
(1)消息
消息定义
消息头
消息种类
(2)编码解码器
序列化输入流
序列化输出流
编码工厂类
消息体编码器
消息体解码器
消息编码器
消息解码器
(3)登录认证
认证请求handler
package com.tyf.loginAuth;import com.tyf.message.Header;import com.tyf.message.MessageType;import com.tyf.message.NettyMessage;import io.netty.channel.ChannelHandler;import io.netty.channel.ChannelHandlerAdapter;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelPipeline;public class LoginAuthReqHandler extends ChannelHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception {ctx.writeAndFlush(buildLoginReq()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {NettyMessage message = (NettyMessage) msg;// 如果是握手应答消息,需要判断是否认证成功if (message.getHeader() != null&& message.getHeader().getType() == MessageType.LOGIN_RESP.value()) { byte loginResult = (Byte) message.getBody(); if (loginResult != (byte) 0) {// 握手失败,关闭连接ctx.close(); } else {System.out.println("Login is ok : " + message);ctx.fireChannelRead(msg); }} else ctx.fireChannelRead(msg); } private NettyMessage buildLoginReq() {NettyMessage message = new NettyMessage();Header header = new Header();header.setType(MessageType.LOGIN_REQ.value());message.setHeader(header);return message; } public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.fireExceptionCaught(cause); }}
(4)心跳检测
心跳请求handler
package com.tyf.heartBeat;import io.netty.channel.ChannelHandlerAdapter;import io.netty.channel.ChannelHandlerContext;import java.util.concurrent.ScheduledFuture;import java.util.concurrent.TimeUnit;import com.tyf.message.Header;import com.tyf.message.MessageType;import com.tyf.message.NettyMessage;/* * * 发送心跳检测请求 * * */public class HeartBeatReqHandler extends ChannelHandlerAdapter { private volatile ScheduledFuture<?> heartBeat; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {NettyMessage message = (NettyMessage) msg;// 握手成功,主动发送心跳消息if (message.getHeader() != null&& message.getHeader().getType() == MessageType.LOGIN_RESP.value()) { heartBeat = ctx.executor().scheduleAtFixedRate( new HeartBeatReqHandler.HeartBeatTask(ctx), 0, 5000, TimeUnit.MILLISECONDS);} else if (message.getHeader() != null&& message.getHeader().getType() == MessageType.HEARTBEAT_RESP.value()) { System.out .println("Client receive server heart beat message : ---> " + message);} else ctx.fireChannelRead(msg); } private class HeartBeatTask implements Runnable {private final ChannelHandlerContext ctx;public HeartBeatTask(final ChannelHandlerContext ctx) { this.ctx = ctx;}public void run() { NettyMessage heatBeat = buildHeatBeat(); System.out .println("Client send heart beat messsage to server : ---> " + heatBeat); ctx.writeAndFlush(heatBeat);}private NettyMessage buildHeatBeat() { NettyMessage message = new NettyMessage(); Header header = new Header(); header.setType(MessageType.HEARTBEAT_REQ.value()); message.setHeader(header); return message;} } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();if (heartBeat != null) { heartBeat.cancel(true); heartBeat = null;}ctx.fireExceptionCaught(cause); }}
(5)client
地址信息
启动类
package com.tyf.client;import io.netty.bootstrap.Bootstrap;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.NioSocketChannel;import io.netty.handler.timeout.ReadTimeoutHandler;import java.net.InetSocketAddress;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;import com.tyf.coder.NettyMessageDecoder;import com.tyf.coder.NettyMessageEncoder;import com.tyf.heartBeat.HeartBeatReqHandler;import com.tyf.loginAuth.LoginAuthReqHandler;public class NettyClient { private ScheduledExecutorService executor = Executors .newScheduledThreadPool(1); EventLoopGroup group = new NioEventLoopGroup(); public void connect(int port, String host) throws Exception {// 配置客户端NIO线程组try { Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch)throws Exception { ch.pipeline().addLast( new NettyMessageDecoder(1024 * 1024, 4, 4));//消息解码 ch.pipeline().addLast("MessageEncoder", new NettyMessageEncoder());//消息编码 ch.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler(50));//会话超时 ch.pipeline().addLast("LoginAuthHandler", new LoginAuthReqHandler());//登录白名单认证 ch.pipeline().addLast("HeartBeatHandler", new HeartBeatReqHandler());//心跳检测} }); // 发起异步连接操作 ChannelFuture future = b.connect( new InetSocketAddress(host, port), new InetSocketAddress(NettyConstant.LOCALIP, NettyConstant.LOCAL_PORT)).sync(); future.channel().closeFuture().sync();} finally { // 所有资源释放完成之后,清空资源,再次发起重连操作 executor.execute(new Runnable() {public void run() { try {TimeUnit.SECONDS.sleep(1);try { connect(NettyConstant.PORT, NettyConstant.REMOTEIP);// 发起重连操作} catch (Exception e) { e.printStackTrace();} } catch (InterruptedException e) {e.printStackTrace(); }} });} } /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception {new NettyClient().connect(NettyConstant.PORT, NettyConstant.REMOTEIP); }}
4.测试
测试服务端宕机客户端在服务端重启后自动重连
客户端不能重复建立连接
客户端与服务端重连期间资源回收不会导致句柄泄露
阅读全文