最近在学习netty,看《netty权威指南》第14章发现给出的例子有些问题,比如有些类根本不存在。并且我也在网上找了一些文章,发现几乎的文章例子都一样,都缺少心跳检测机制。具体网上的例子此处不在重复。
现在我把代码贴出来,结合《netty权威指南》和文章的详细代码,代码可直接运行。
看过《netty权威指南》的同学应该会对以下类的定义有些了解。
1.定义消息NettyMessage和消息头Header
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
public class NettyMessage {
private Header header;
private Object body;
public Header getHeader() {
return header;
}
public void setHeader(Header header) {
this.header = header;
}
public Object getBody() {
return body;
}
public void setBody(Object body) {
this.body = body;
}
@Override
public String toString() {
return "header:" + header + ",body:" + body;
}
}
</span></span>
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
import java.util.HashMap;
import java.util.Map;
public 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>();
public int getCrcCode() {
return crcCode;
}
public void setCrcCode(int crcCode) {
this.crcCode = crcCode;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public long getSessionId() {
return sessionId;
}
public void setSessionId(long sessionId) {
this.sessionId = sessionId;
}
public byte getType() {
return type;
}
public void setType(byte type) {
this.type = type;
}
public byte getPriority() {
return priority;
}
public void setPriority(byte priority) {
this.priority = priority;
}
public Map<String, Object> getAttachment() {
return attachment;
}
public void setAttachment(Map<String, Object> attachment) {
this.attachment = attachment;
}
@Override
public String toString() {
return "Header [crcCode=" + crcCode + ", length=" + length
+ ", sessionId=" + sessionId + ", type=" + type + ", priority="
+ priority + ", attachment=" + attachment + "]";
}
}
</span></span>
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.marshalling.MarshallingDecoder;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;
public class NettyMarshallingDecoder extends MarshallingDecoder{
public NettyMarshallingDecoder(UnmarshallerProvider provider) {
super(provider);
}
public NettyMarshallingDecoder(UnmarshallerProvider provider, int maxObjectSize) {
super(provider, maxObjectSize);
}
@Override
public Object decode(ChannelHandlerContext arg0, ByteBuf arg1)
throws Exception {
return super.decode(arg0, arg1);
}
}
</span></span>
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallingEncoder;
public class NettyMarshallingEncoder extends MarshallingEncoder{
public NettyMarshallingEncoder(MarshallerProvider provider) {
super(provider);
}
@Override
public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out)
throws Exception {
super.encode(ctx, msg, out);
}
}
</span></span>
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
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;
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, 10240);
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;
}
}
</span></span>
4.定义NettyMessageDecoder(消息解码),NettyMessageEncoder(消息编码),代码挺好理解,稍微繁琐点就是对消息扩展的操作
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import java.util.HashMap;
import java.util.Map;
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
public Object decode(ChannelHandlerContext ctx, ByteBuf in)
throws Exception {
ByteBuf 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> attach = new HashMap<String, Object>();
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");
attach.put(key, marshallingDecoder.decode(ctx, frame));
}
key = null;
keyArray = null;
header.setAttachment(attach);
}
if (frame.readableBytes() > 0) {
message.setBody(marshallingDecoder.decode(ctx, frame));
}
message.setHeader(header);
return message;
}
}
</span></span>
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.marshalling.MarshallingEncoder;
import java.util.List;
import java.util.Map;
public class NettyMessageEncoder extends MessageToMessageEncoder<NettyMessage>{
NettyMarshallingEncoder marshallingEncoder;
public NettyMessageEncoder(){
marshallingEncoder = MarshallingCodeCFactory.buildMarshallingEncoder();
}
@Override
protected void encode(ChannelHandlerContext ctx, NettyMessage msg,
List<Object> list) throws Exception {
if (msg == null || msg.getHeader() == null) {
throw new Exception("the encode message is null");
}
ByteBuf buf = Unpooled.buffer();
buf.writeInt(msg.getHeader().getCrcCode());
buf.writeInt(msg.getHeader().getLength());
buf.writeLong(msg.getHeader().getSessionId());
buf.writeByte(msg.getHeader().getType());
buf.writeByte(msg.getHeader().getPriority());
buf.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");
buf.writeInt(keyArray.length);
buf.writeBytes(keyArray);
value = param.getValue();
marshallingEncoder.encode(ctx, value, buf);
}
key = null;
keyArray = null;
value = null;
if (msg.getBody() != null) {
marshallingEncoder.encode(ctx, msg.getBody(), buf);
}
int readableBytes = buf.readableBytes();
buf.setInt(4, readableBytes);
list.add(buf);
}
}
</span></span>
5.定义LoginAuthReqHandler(握手请求认证),LoginAuthRespHandler(握手应答认证)
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
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() == 4) {
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);
}
}
public NettyMessage buildLoginReq() {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType((byte)3); //3(握手请求消息)
message.setHeader(header);
return message;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.fireExceptionCaught(cause);
}
}
</span></span>
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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() == 3) {
String nodeIndex = ctx.channel().remoteAddress().toString();
NettyMessage loginResult = null;
if (nodeCheck.containsKey(nodeIndex)) {
loginResult = buildRespon((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;
}
}
loginResult = isOK ? buildRespon((byte)0) : buildRespon((byte)-1);
if (isOK) {
nodeCheck.put(nodeIndex, true);
}
}
System.out.println("the login response is : " + loginResult);
ctx.writeAndFlush(loginResult);
} else {
ctx.fireChannelRead(msg);
}
}
private NettyMessage buildRespon(byte result) {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType((byte)4); //握手应答消息
message.setHeader(header);
message.setBody(result);
return message;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
nodeCheck.remove(ctx.channel().remoteAddress().toString()); //出现异常删除缓存
ctx.close();
ctx.fireExceptionCaught(cause);
}
}
</span></span>
6.定义心跳监测机制 HeartBeatReqHandler(心跳请求),HeartBeatRespHandler(心跳应答)
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
import java.util.concurrent.TimeUnit;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.ScheduledFuture;
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() == 4) {
heartBeat = ctx.executor().scheduleAtFixedRate(new HeartBeatReqHandler.HeartBeatTask(ctx), 0, 5000, TimeUnit.MILLISECONDS);
} else if (message.getHeader() != null && message.getHeader().getType() == 6) {
System.out.println("client receive server heart message : " + message);
} else {
ctx.fireChannelRead(msg);
}
}
private class HeartBeatTask implements Runnable {
private final ChannelHandlerContext ctx;
public HeartBeatTask(final ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void run() {
NettyMessage message = buildHeatBeat();
System.out.println("client send heart message : " + message);
ctx.writeAndFlush(message);
}
private NettyMessage buildHeatBeat() {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType((byte)5); //心跳请求消息
message.setHeader(header);
return message;
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
if (heartBeat != null) {
heartBeat.cancel(true);
heartBeat = null;
}
ctx.fireExceptionCaught(cause);
}
}
</span></span>
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
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() == 5) {
System.out.println("server receive client heart message " + message);
NettyMessage heartBeat = buildHeartBeat();
ctx.writeAndFlush(heartBeat);
} else {
ctx.fireChannelRead(msg);
}
}
public NettyMessage buildHeartBeat() {
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType((byte)6); //心跳应答消息
message.setHeader(header);
return message;
}
}
</span></span>
7.定义 NettyClient(客户端) ,NettyServer(服务端)
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
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.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class NettyClient {
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
EventLoopGroup group = new NioEventLoopGroup();
public void connect(final int port, final String host) {
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new NettyMessageDecoder(1024*1024, 4, 4, -8, 0));
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 f = b.connect(host,port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
executor.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(5);
connect(port, host);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
public static void main(String[] args) {
int port = 8080;
new NettyClient().connect(port, "127.0.0.1");
}
}
</span></span>
<span style="font-size:18px;"><span style="font-size:18px;">package com.netty.msg;
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;
public class NettyServer {
public void bind(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
try {
b.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
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("readTimeoutHandler", new ReadTimeoutHandler(50));
ch.pipeline().addLast(new LoginAuthRespHandler());
ch.pipeline().addLast("HeartBeatHandler", new HeartBeatRespHandler());
}
});
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
int port = 8080;
new NettyServer().bind(port);
}
}
</span></span>
运行结果可以看到握手成功,并且答应相应的日志。每5秒发送一次心跳检测。服务端关闭,客户端不再发送心跳,并报错。服务端重启看输出日志可看到握手成功继续发送心跳。
需要的jar包:jboss-marshalling-1.3.0.CR9.jarjboss-marshalling-serial-1.3.0.CR9.jar netty-all-5.0.0.Alpha2.jar
有问题望指出纠正。