本文参考《Netty权威指南》
私有协议实现的功能:
1、基于Netty的NIO通信框架,提供高性能异步通信能力
2、提供消息的编码解码框架,实现POJO的序列化和反序列化
3、提供基于IP地址的白名单接入认证机制
4、链路有效性校验机制
5、断路重连机制
协议模型:
消息定义:
NettyMessage
名称 | 类型 | 长度 | 描述 |
header | Header | 变长 | 消息头定义 |
body | Object | 变长 | 请求消息:参数 响应消息:返回值 |
Header
名称 | 类型 | 长度 | 描述 |
crcCode | int | 32 | Netty消息校验码(三部分) 1、0xABEF:固定值,表明消息是Netty协议消息,2字节 2、主版本号:1~255,1字节 3、次版本号:1~255,1字节 crcCode=0xABEF+主版本号+次版本号 |
length | int | 32 | 消息长度,包括消息头,消息体 |
sessionID | long | 64 | 集群节点全局唯一,由会话生成器生成 |
type | Byte | 8 | 0:业务请求消息 1:业务响应消息 2:业务ONE-WAY消息(既是请求又是响应) 3:握手请求消息 4:握手应答消息 5:心跳请求消息 6:心跳应答消息 |
priority | Byte | 8 | 消息优先级:0~255 |
attachment | Mep<String,Object> | 变长 | 可选,用于扩展消息头 |
参考:http://blog.csdn.net/iter_zc/article/details/39317311
依赖:
├── jboss-marshalling-1.3.0.CR9.jar
├── jboss-marshalling-serial-1.3.0.CR9.jar
└── netty-all-5.0.0.Alpha2.jar
源码:
├── Header.java
├── HeartBeatReqHandler.java
├── HeartBeatRespHandler.java
├── LoginAuthReqHandler.java
├── LoginAuthRespHandler.java
├── MarshallingCodeCFactory.java
├── MessageType.java
├── NettyClient.java
├── NettyConstants.java
├── NettyMarshallingDecoder.java
├── NettyMarshallingEncoder.java
├── NettyMessageDecoder.java
├── NettyMessageEncoder.java
├── NettyMessage.java
└── NettyServer.java
package com.xh.netty.test14;
import java.util.HashMap;
import java.util.Map;
/**
* 消息头
* Created by root on 1/11/18.
*/
public final class Header {
private int crcCode = 0xabef0101;
private int length;
private long sessionID;
private byte type;//消息类型
private byte priority;//消息优先级
private Map<String, Object> attachment = new HashMap<String, Object>();//附件
...//set get
@Override
public String toString() {
...
}
}
package com.xh.netty.test14;
/**
* 消息
* Created by root on 1/11/18.
*/
public final class NettyMessage {
private Header header;
private Object body;
...//set get
@Override
public String toString() {
...
}
}
package com.xh.netty.test14;
/**
* 消息类型
* Created by root on 1/12/18.
*/
public enum MessageType {
//心跳请求,应答
HEARTBEAT_REQ((byte) 5),
HEARTBEAT_RESP((byte) 6),
//握手请求,应答
LOGIN_REQ((byte) 3),
LOGIN_RESP((byte) 4);
byte value;
MessageType(byte value) {
this.value = value;
}
}
package com.xh.netty.test14;
/**
* 常量
* Created by root on 1/12/18.
*/
public class NettyConstants {
public static int LOCAL_PORT = 8080;
public static String LOCAL_IP = "127.0.0.1";
public static int REMOTE_PORT = 80;
public static String REMOTE_IP = "127.0.0.1";
}
package com.xh.netty.test14;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List;
import java.util.Map;
/**
* 消息编码器
* Created by root on 1/11/18.
*/
public final class NettyMessageEncoder extends MessageToMessageEncoder<NettyMessage> {
NettyMarshallingEncoder marshallingEncoder;
/**
* 这里和书中不一样,可能有问题
*/
public NettyMessageEncoder() {
//this.marshallingEncoder = new MarshallingEncoder(new DefaultMarshallerProvider(new SerialMarshallerFactory(), new MarshallingConfiguration()));
this.marshallingEncoder = MarshallingCodeCFactory.buildMarshallingEncoder();
}
protected void encode(ChannelHandlerContext ctx, NettyMessage nettyMessage, List<Object> list) throws Exception {
if (nettyMessage == null || nettyMessage.getHeader() == null) {
throw new Exception("the encode message is null");
}
ByteBuf sendBuf = Unpooled.buffer();
sendBuf.writeInt(nettyMessage.getHeader().getCrcCode());
sendBuf.writeInt(nettyMessage.getHeader().getLength());
sendBuf.writeLong(nettyMessage.getHeader().getSessionID());
sendBuf.writeByte(nettyMessage.getHeader().getType());
sendBuf.writeByte(nettyMessage.getHeader().getPriority());
sendBuf.writeInt(nettyMessage.getHeader().getAttachment().size());
String key = null;
byte[] keyArray = null;
Object value = null;
for (Map.Entry<String, Object> param : nettyMessage.getHeader().getAttachment().entrySet()) {
key = param.getKey();
keyArray = key.getBytes("UTF-8");
sendBuf.writeInt(keyArray.length);
sendBuf.writeBytes(keyArray);
value = param.getValue();
marshallingEncoder.encode(ctx, value, sendBuf);
}
key = null;
keyArray = null;
value = null;
if (nettyMessage.getBody() != null) {
marshallingEncoder.encode(ctx, nettyMessage.getBody(), sendBuf);
} else {
sendBuf.writeInt(0);
}
// 在第4个字节出写入Buffer的长度
int readableBytes = sendBuf.readableBytes();
sendBuf.setInt(4, readableBytes);
// 把Message添加到List传递到下一个Handler
list.add(sendBuf);
}
}
package com.xh.netty.test14;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallingEncoder;
/**
* Created by root on 1/11/18.
*/
public class NettyMarshallingEncoder extends MarshallingEncoder {
public NettyMarshallingEncoder(MarshallerProvider provider) {
super(provider);
}
public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
super.encode(ctx, msg, out);
}
}
package com.xh.netty.test14;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import java.util.HashMap;
import java.util.Map;
/**
* 消息解码器
* Created by root on 1/11/18.
*/
public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder {
private NettyMarshallingDecoder marshallingDecoder;
public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset,
int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
marshallingDecoder = MarshallingCodeCFactory.buildMarshallingDecoder();
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
System.out.println("NettyMessageDecoder 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 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];
in.readBytes(keyArray);
key = new String(keyArray, "UTF-8");
attch.put(key, marshallingDecoder.decode(ctx, frame));
}
key = null;
keyArray = null;
header.setAttachment(attch);
}
if (frame.readableBytes() > 0) {
message.setBody(marshallingDecoder.decode(ctx, frame));
}
message.setHeader(header);
return message;
}
}
package com.xh.netty.test14;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.marshalling.MarshallingDecoder;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;
/**
* Created by root on 1/11/18.
*/
public class NettyMarshallingDecoder extends MarshallingDecoder {
public NettyMarshallingDecoder(UnmarshallerProvider provider) {
super(provider);
}
public NettyMarshallingDecoder(UnmarshallerProvider provider, int maxObjectSize){
super(provider, maxObjectSize);
}
public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
return super.decode(ctx, in);
}
}
package com.xh.netty.test14;
import io.netty.handler.codec.marshalling.DefaultMarshallerProvider;
import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
/**
* Created by root on 1/11/18.
*/
public class MarshallingCodeCFactory {
public static NettyMarshallingDecoder buildMarshallingDecoder() {
MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
NettyMarshallingDecoder decoder = new NettyMarshallingDecoder(provider, 1024);
return decoder;
}
public static NettyMarshallingEncoder buildMarshallingEncoder() {
MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
NettyMarshallingEncoder encoder = new NettyMarshallingEncoder(provider);
return encoder;
}
}
package com.xh.netty.test14;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 握手认证(client)
* Created by root on 1/12/18.
*/
public class LoginAuthReqHandler extends ChannelHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//握手请求
ctx.writeAndFlush(buildLoginReq());
System.out.println("client: send LoginAuthReq ---> " + buildLoginReq());
}
private NettyMessage buildLoginReq() {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType((byte) 3);
message.setHeader(header);
message.setBody((byte) 0);
return message;
}
@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) {
//如果应答消息体不为0则认证失败
ctx.close();
} else {
System.out.println("client:Login is OK --->" + message);
ctx.fireChannelRead(message);
}
} else {
//通知下一个Handler
ctx.fireChannelRead(message);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
package com.xh.netty.test14;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
/**
* 握手认证(server)
* Created by root on 1/12/18.
*/
public class LoginAuthRespHandler extends ChannelHandlerAdapter {
//已登录列表
Map node = new HashMap<String, Boolean>();
//白名单
private String[] whiteList = {"127.0.0.1"};
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyMessage message = (NettyMessage) msg;
//握手请求消息
if (message != null && message.getHeader().getType() == MessageType.LOGIN_REQ.value) {
String nodeInde = ctx.channel().remoteAddress().toString();
System.out.println("server:receive LoginAuthReq <--- " + message);
NettyMessage loginResp = null;
//拒绝重复登录
if (node.containsKey(nodeInde)) {
loginResp = buildResp((byte) -1);
} else {
InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
Boolean isOK = false;
String IP = address.getAddress().getHostAddress();
for (String WIP : whiteList) {
if (WIP.equalsIgnoreCase(IP)) {
isOK = true;
break;
}
}
loginResp = isOK ? buildResp((byte) 0) : buildResp((byte) -1);
if (isOK) {
node.put(nodeInde, true);
}
}
System.out.println("server:send LoginAuthResp ---> " + loginResp);
ctx.writeAndFlush(loginResp);
} else {
ctx.fireChannelRead(message);
}
}
private NettyMessage buildResp(byte b) {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType(MessageType.LOGIN_RESP.value);
message.setHeader(header);
message.setBody(b);
return message;
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//删除缓存
node.remove(ctx.channel().remoteAddress().toString());
ctx.close();
ctx.fireExceptionCaught(cause);
}
}
package com.xh.netty.test14;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* 心跳检测(client)
* Created by root on 1/12/18.
*/
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 HEARTBEAT_RESP ---> " + message);
} else {
ctx.fireChannelRead(message);
}
}
private class HeartBeatTask implements Runnable {
private ChannelHandlerContext ctx;
public HeartBeatTask(final ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public void run() {
NettyMessage message = buildHeartBeat();
System.out.println("client: send HeartBeat to server --->" + message);
ctx.writeAndFlush(message);
}
private NettyMessage buildHeartBeat() {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType(MessageType.HEARTBEAT_REQ.value);
message.setHeader(header);
message.setBody((byte) 0);
return message;
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (heartBeat != null) {
heartBeat.cancel(true);
heartBeat = null;
}
ctx.fireExceptionCaught(cause);
}
}
package com.xh.netty.test14;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 心跳检测(server)
* Created by root on 1/12/18.
*/
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("server:receive client HeartBeat ---> " + message);
NettyMessage heartBeat = buildHeartBeat();
ctx.writeAndFlush(heartBeat);
System.out.println("server:send HeartBeat to client ---> " + heartBeat);
} else {
ctx.fireChannelRead(msg);
}
}
private NettyMessage buildHeartBeat() {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType(MessageType.HEARTBEAT_RESP.value);
message.setHeader(header);
message.setBody((byte) 0);
return message;
}
}
package com.xh.netty.test14;
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;
/**
* 客户端
* Created by root on 1/11/18.
*/
public class NettyClient {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
EventLoopGroup group = new NioEventLoopGroup();
public void connect(int port, String host) {
//client NIO thread
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyMessageDecoder(1024 * 1024, 4, 4, -8, 0));
ch.pipeline().addLast(new NettyMessageEncoder());
ch.pipeline().addLast(new ReadTimeoutHandler(50));
ch.pipeline().addLast(new LoginAuthReqHandler());
ch.pipeline().addLast(new HeartBeatReqHandler());
}
});
//异步连接
ChannelFuture future = b.connect(
new InetSocketAddress(host, port)
//指定本地的端口
//new InetSocketAddress(NettyConstants.LOCAL_IP, NettyConstants.LOCAL_PORT)
).sync();
System.out.println("client: connect to server host:" + host + ", port:" + port);
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放资源,重连
executorService.execute(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(5);
//重连
connect(NettyConstants.REMOTE_PORT, NettyConstants.REMOTE_IP);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
public static void main(String[] args) {
new NettyClient().connect(NettyConstants.REMOTE_PORT, NettyConstants.REMOTE_IP);
}
}
package com.xh.netty.test14;
import io.netty.bootstrap.ServerBootstrap;
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.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
/**
* 服务端
* Created by root on 1/11/18.
*/
public class NettyServer {
public void bind(int port) throws InterruptedException {
EventLoopGroup bossGrop = new NioEventLoopGroup();
EventLoopGroup workerGrop = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGrop, workerGrop).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyMessageDecoder(1024 * 1024, 4, 4, -8, 0));
ch.pipeline().addLast(new NettyMessageEncoder());
ch.pipeline().addLast(new ReadTimeoutHandler(50));
ch.pipeline().addLast(new LoginAuthRespHandler());
ch.pipeline().addLast(new HeartBeatRespHandler());
}
});
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("server: start ok ! host:" + NettyConstants.REMOTE_IP + ",port:" + NettyConstants.REMOTE_PORT);
future.channel().closeFuture().sync();
}
public static void main(String[] args) throws InterruptedException {
new NettyServer().bind(NettyConstants.REMOTE_PORT);
}
}