Netty自带心跳检测功能,IdleStateHandler,客户端在写空闲时主动发起心跳请求,服务器接受到心跳请求后给出一个心跳响应。当客户端在一定时间范围内不能够给出响应则断开链接。
public class NettyClient {
public void connect(String remoteServer, int port) throws Exception {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup).channel(NioSocketChannel.class).remoteAddress(remoteServer, port)
.handler(new ChildChannelHandler());
ChannelFuture f = b.connect();
System.out.println("Netty time Client connected at port " + port);
f.channel().closeFuture().sync();
} finally {
try {
TimeUnit.SECONDS.sleep(5);
try {
System.out.println("重新链接。。。");
connect(remoteServer, port);
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static class ChildChannelHandler extends ChannelInitializer {
@Override
protected void initChannel(final SocketChannel ch) throws Exception {
// -8表示lengthAdjustment,让解码器从0开始截取字节,并且包含消息头
ch.pipeline().addLast(new RpcEncoder(NettyMessage.class)).addLast(new RpcDecoder(NettyMessage.class))
.addLast(new IdleStateHandler(120, 10, 0, TimeUnit.SECONDS)).addLast(new HeartBeatReqHandler());
}
}
public static void main(String[] args) {
try {
new NettyClient().connect("127.0.0.1", 12000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class SerializationUtil {
private static Map, Schema>> cachedSchema = new ConcurrentHashMap, Schema>>();
private static Objenesis objenesis = new ObjenesisStd(true);
private static Schema getSchema(Class clazz) {
@SuppressWarnings("unchecked")
Schema schema = (Schema) cachedSchema.get(clazz);
if (schema == null) {
schema = RuntimeSchema.getSchema(clazz);
if (schema != null) {
cachedSchema.put(clazz, schema);
}
}
return schema;
}
/**
* 序列化
*
* @param obj
* @return
*/
public static byte[] serializer(T obj) {
@SuppressWarnings("unchecked")
Class clazz = (Class) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Schema schema = getSchema(clazz);
byte result[] = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
return result;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
/**
* 反序列化
*
* @param data
* @param clazz
* @return
*/
public static T deserializer(byte[] data, Class clazz) {
try {
T obj = objenesis.newInstance(clazz);
Schema schema = getSchema(clazz);
ProtostuffIOUtil.mergeFrom(data, obj, schema);
return obj;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
@SuppressWarnings("rawtypes")
public class RpcEncoder extends MessageToByteEncoder {
private Class> genericClass;
public RpcEncoder(Class> genericClass) {
this.genericClass = genericClass;
}
@Override
public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
if (genericClass.isInstance(in)) {
System.out.println("发送的请求是:"+in);
byte[] data = SerializationUtil.serializer(in);
out.writeInt(data.length);
out.writeBytes(data);
}
}
}
public class RpcDecoder extends ByteToMessageDecoder {
private Class> genericClass;
public RpcDecoder(Class> genericClass) {
this.genericClass = genericClass;
}
@Override
public final void decode(ChannelHandlerContext ctx, ByteBuf in, List
public class HeartBeatReqHandler extends ChannelDuplexHandler {
/**
* @see io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.channel.ChannelHandlerContext,
* java.lang.Object)
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
System.out.println("read 空闲");
ctx.disconnect();
} else if (event.state() == IdleState.WRITER_IDLE) {
System.out.println("write 空闲");
ctx.writeAndFlush(buildHeartBeat(MessageType.HEARTBEAT_REQ.getType()));
}
}
}
/**
*
* @return
* @author zhangwei
*/
private NettyMessage buildHeartBeat(byte type) {
NettyMessage msg = new NettyMessage();
Header header = new Header();
header.setType(type);
msg.setHeader(header);
return msg;
}
}
public class NettyServer {
public void bind(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
ChannelFuture f = b.bind(port).sync();
System.out.println("Netty time Server started at port " + port);
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static class ChildChannelHandler extends ChannelInitializer {
@Override
protected void initChannel(final SocketChannel ch) throws Exception {
ch.pipeline().addLast(new RpcDecoder(NettyMessage.class)).addLast(new RpcEncoder(NettyMessage.class))
.addLast(new IdleStateHandler(120, 0, 0, TimeUnit.SECONDS)).addLast(new HeartBeatRespHandler());
}
}
public static void main(String[] args) {
try {
new NettyServer().bind(12000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public enum MessageType {
LOGIN_REQ((byte) 1), LOGIN_RESP((byte) 2), HEARTBEAT_REQ((byte) 3), HEARTBEAT_RESP((byte) 4);
private byte type;
/**
* @param type
*/
private MessageType(byte type) {
this.type = type;
}
public byte getType() {
return type;
}
public void setType(byte type) {
this.type = type;
}
public static MessageType getMessageType(byte type) {
for (MessageType b : MessageType.values()) {
if (b.getType() == type) {
return b;
}
}
return null;
}
}
public class HeartBeatRespHandler extends SimpleChannelInboundHandler {
/**
* @see io.netty.channel.SimpleChannelInboundHandler#channelRead0(io.netty.channel.ChannelHandlerContext,
* java.lang.Object)
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, NettyMessage msg) throws Exception {
if (msg.getHeader() != null && msg.getHeader().getType() == MessageType.HEARTBEAT_REQ.getType()) {
NettyMessage heartBeat = buildHeartBeat(MessageType.HEARTBEAT_RESP.getType());
ctx.writeAndFlush(heartBeat);
} else {
ctx.fireChannelRead(msg);
}
}
/**
* @see io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.channel.ChannelHandlerContext,
* java.lang.Object)
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
System.out.println("read 空闲 关闭链接");
ctx.disconnect();
}
}
}
/**
*
* @return
* @author zhangwei
*/
private NettyMessage buildHeartBeat(byte type) {
NettyMessage msg = new NettyMessage();
Header header = new Header();
header.setType(type);
msg.setHeader(header);
return msg;
}
}
public class NettyMessage implements Serializable{
/** */
private static final long serialVersionUID = 1L;
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;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "NettyMessage [header=" + header + ", body=" + body + "]";
}
}
public class Header implements Serializable{
/** */
private static final long serialVersionUID = 1L;
private int crcCode=0xabef0101;
private int length;
private long sessionId;
private byte type;
private byte priority;
private Map attachment=new HashMap<>();
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 getAttachment() {
return attachment;
}
public void setAttachment(Map attachment) {
this.attachment = attachment;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Header [crcCode=" + crcCode + ", length=" + length + ", sessionId=" + sessionId + ", type=" + type
+ ", priority=" + priority + ", attachment=" + attachment + "]";
}
}
客户端的结果是:
etty time Client connected at port 12000
write 空闲
发送的请求是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=3, priority=0, attachment={}], body=null]
接收到的消息是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=4, priority=0, attachment={}], body=null]
write 空闲
发送的请求是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=3, priority=0, attachment={}], body=null]
接收到的消息是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=4, priority=0, attachment={}], body=null]
write 空闲
发送的请求是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=3, priority=0, attachment={}], body=null]
接收到的消息是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=4, priority=0, attachment={}], body=null]
write 空闲
发送的请求是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=3, priority=0, attachment={}], body=null]
接收到的消息是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=4, priority=0, attachment={}], body=null]
write 空闲
发送的请求是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=3, priority=0, attachment={}], body=null]
接收到的消息是:NettyMessage [header=Header [crcCode=-1410399999, length=0, sessionId=0, type=4, priority=0, attachment={}], body=null]