io.netty
netty-all
4.1.19.Final
org.msgpack
msgpack
0.6.7
com.google.protobuf
protobuf-java
2.5.0
org.projectlombok
lombok
1.14.8
org.jibx
jibx-bind
1.3.1
org.jboss.marshalling
jboss-marshalling-serial
1.4.11.Final
重点关注:jboss-marshalling-serial包,一开始我导的不是这个,是上面注释的jboss-marshalling,
结果导致Server无法向Client发送消息。
原因是下面的MarshallingCodeCFactory类,获取marshallerFactory是返回null,没获取到!
MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
package com.xkl.learn.privateprotocol.tool;
import java.io.IOException;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
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;
/**
* Created by xiekunliang on 2018/2/6.
*/
public class MarshallingCodeCFactory
{
public static NettyMarshallingDecoder buildMarshallingDecoder() throws IOException
{
MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
NettyMarshallingDecoder decoder = new NettyMarshallingDecoder(provider, 1024 << 2);
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.xkl.learn.privateprotocol.tool;
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 xiekunliang on 2018/2/5.
* 编码器
*/
public class NettyMarshallingEncoder extends MarshallingEncoder
{
/**
* Creates a new encoder.
*
* @param provider the {@link MarshallerProvider} to use
*/
public NettyMarshallingEncoder(MarshallerProvider provider)
{
super(provider);
}
@Override
public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) {
try {
super.encode(ctx, msg, out);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.xkl.learn.privateprotocol.tool;
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 xiekunliang on 2018/2/5.
* 解码器
*/
public class NettyMarshallingDecoder extends MarshallingDecoder
{
public NettyMarshallingDecoder(UnmarshallerProvider provider, int objectMaxSize) {
super(provider, objectMaxSize);
}
public NettyMarshallingDecoder(UnmarshallerProvider provider) {
super(provider);
}
@Override
public Object decode(ChannelHandlerContext ctx, ByteBuf in) {
try {
return super.decode(ctx, in);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.xkl.learn.privateprotocol;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.xkl.learn.privateprotocol.tool.MarshallingCodeCFactory;
import com.xkl.learn.privateprotocol.tool.NettyMarshallingEncoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
/**
* Created by xiekunliang on 2018/2/5.
*/
public class NettyMessageEncoder extends MessageToMessageEncoder
{
NettyMarshallingEncoder nettyMarshallingEncoder;
public NettyMessageEncoder() throws IOException
{
nettyMarshallingEncoder = MarshallingCodeCFactory.buildMarshallingEncoder();
}
@Override
protected void encode(ChannelHandlerContext ctx, NettyMessage msg, List
package com.xkl.learn.privateprotocol;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.xkl.learn.privateprotocol.tool.MarshallingCodeCFactory;
import com.xkl.learn.privateprotocol.tool.NettyMarshallingDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
/**
* Created by xiekunliang on 2018/2/7.
*/
public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder
{
NettyMarshallingDecoder marshallingDecoder;
public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip) throws IOException
{
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("解码器返回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 attachSize = frame.readInt();
if (attachSize > 0)
{
Map attachment = new HashMap<>();
for (int i = 0; i < attachSize; i++)
{
int keySize = frame.readInt();
byte[] keyArray = new byte[keySize];
frame.readBytes(keyArray);
String key = new String(keyArray, "UTF-8");
Object val = marshallingDecoder.decode(ctx, frame);
attachment.put(key, val);
}
header.setAttachment(attachment);
}
if (frame.readableBytes() > 0)
{
message.setBody(marshallingDecoder.decode(ctx, frame));
}
message.setHeader(header);
return message;
}
}
package com.xkl.learn.privateprotocol;
import lombok.Data;
/**
* 消息s
*/
@Data
public final class NettyMessage
{
private Header header;
private Object body;
}
package com.xkl.learn.privateprotocol;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
/**
* Created by xiekunliang on 2018/2/5.
*/
@Data
public class Header
{
private int crcCode = 0xabef0101;
// 消息长度
private int length;
private long sessionID;
/**
* @see MessageType
*/
private byte type;
private byte priority;
private Map attachment = new HashMap<>();
}
package com.xkl.learn.privateprotocol;
public interface MessageType
{
Byte BUSINESS_REQ = 0;
Byte BUSINESS_RESP = 1;
Byte BUSINESS_ONE_WAY = 2;
Byte LOGIN_REQ = 3;
Byte LOGIN_RESP = 4;
Byte HEARTBEAT_REQ = 5;
Byte HEARTBEAT_RESP = 6;
}
package com.xkl.learn.privateprotocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Created by xiekunliang on 2018/2/24.
*/
public class LoginAuthReqHandler extends ChannelInboundHandlerAdapter
{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception
{
NettyMessage message = buildLoginReq();
ctx.writeAndFlush(message);
System.out.println("客户端发送握手请求:" + 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)
{
if (message.getBody() != null)
{
String loginResult = message.getBody().toString();
if (loginResult.equals("login_ok"))
{
System.out.println("Login is success :" + message);
// 透传给后面的Handler处理
ctx.fireChannelRead(msg);
}
else
{
// 握手失败,关闭连接
ctx.close();
System.out.println("握手失败,关闭连接");
}
}
else
{
// 握手失败,关闭连接
ctx.close();
System.out.println("握手失败,关闭连接");
}
}
else
{
// 如果不是握手应答消息,直接透传
ctx.fireChannelRead(msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception
{
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
ctx.fireExceptionCaught(cause);
}
private NettyMessage buildLoginReq()
{
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType(MessageType.LOGIN_REQ);
message.setHeader(header);
return message;
}
}
package com.xkl.learn.privateprotocol;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Created by xiekunliang on 2018/2/24.
*/
public class LoginAuthRespHandler extends ChannelInboundHandlerAdapter
{
// 白名单,暂时简单处理,写死了
private String[] writeList = { "/127.0.0.1" };
// 已经登录的IP缓存,防止重复登录
private Map nodeCheck = new ConcurrentHashMap<>();
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception
{
ctx.flush();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
{
NettyMessage message = (NettyMessage) msg;
// 如果是握手请求消息
if (message.getHeader() != null && message.getHeader().getType() == MessageType.LOGIN_REQ)
{
System.out.println("receive client login req : " + message);
NettyMessage loginResp = null;
String nodeIndex = ctx.channel().remoteAddress().toString().split(":")[0];
// 判断是否重复登录
if (nodeCheck.containsKey(nodeIndex))
{
loginResp = buildResponse("login_repeat");
}
else
{
// 判断是否属于白名单中的IP
boolean tag = false;
for (String ip : writeList)
{
if (ip.equals(nodeIndex))
{
tag = true;
break;
}
}
if (tag)
{
nodeCheck.put(nodeIndex, true);
loginResp = buildResponse("login_ok");
}
else
{
loginResp = buildResponse("login_fail");
}
ctx.writeAndFlush(loginResp);
System.out.println("send login resp is : " + loginResp);
}
}
else
{
ctx.fireChannelRead(msg);
}
}
private NettyMessage buildResponse(String result)
{
NettyMessage message = new NettyMessage();
Header header = new Header();
header.setType(MessageType.LOGIN_RESP);
message.setHeader(header);
message.setBody(result);
return message;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
try
{
ctx.fireExceptionCaught(cause);
}
finally
{
// 异常发生时,把登录缓存清除
nodeCheck.remove("/127.0.0.1");
}
}
}
package com.xkl.learn.privateprotocol;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class HeartBeatReqHandler extends ChannelInboundHandlerAdapter
{
private volatile ScheduledFuture> heartBeat;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
{
// 如果握手成功,主动发送心跳消息
NettyMessage message = (NettyMessage) msg;
Header header = message.getHeader();
if (header != null)
{
if (header.getType() == MessageType.LOGIN_RESP)
{
if (heartBeat == null)
{
heartBeat = ctx.executor().scheduleAtFixedRate(new HeartBeatTask(ctx),
0,
5000,
TimeUnit.MILLISECONDS);
}
}
else if (header.getType() == MessageType.HEARTBEAT_RESP)
{
System.out.println("Client receive server heart beat message :" + message);
}
else
{
ctx.fireChannelRead(msg);
}
}
else
{
ctx.fireExceptionCaught(new Throwable("没有Header"));
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
try
{
ctx.fireExceptionCaught(cause);
}
finally
{
closeHeartBeat();
}
}
private void closeHeartBeat()
{
if (heartBeat != null)
{
heartBeat.cancel(true);
heartBeat = null;
}
}
private class HeartBeatTask implements Runnable
{
final ChannelHandlerContext ctx;
public HeartBeatTask(final ChannelHandlerContext ctx)
{
this.ctx = ctx;
}
@Override
public void run()
{
ctx.writeAndFlush(buildHeartBeat());
System.out.println("Client send heart beat message to server : ----> " + heartBeat);
}
}
private NettyMessage buildHeartBeat()
{
NettyMessage heartBeat = new NettyMessage();
Header header = new Header();
header.setType(MessageType.HEARTBEAT_REQ);
heartBeat.setHeader(header);
heartBeat.setBody(null);
return heartBeat;
}
}
package com.xkl.learn.privateprotocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class HeartBeatRespHandler extends ChannelInboundHandlerAdapter
{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
{
NettyMessage message = (NettyMessage) msg;
if (message.getHeader() != null && message.getHeader().getType() == MessageType.HEARTBEAT_REQ)
{
System.out.println("Receive client heart beat message");
ctx.writeAndFlush(buildHeartBeat());
System.out.println("Send heart beat message to client");
} else {
ctx.fireChannelRead(msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
ctx.fireExceptionCaught(cause);
}
private NettyMessage buildHeartBeat()
{
NettyMessage heartBeat = new NettyMessage();
Header header = new Header();
header.setType(MessageType.HEARTBEAT_RESP);
heartBeat.setHeader(header);
heartBeat.setBody(null);
return heartBeat;
}
}
package com.xkl.learn.privateprotocol;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
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;
/**
* Created by xiekunliang on 2018/2/24.
*/
public class NettyClient
{
private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
private EventLoopGroup group = new NioEventLoopGroup();
public static void main(String[] args) throws Exception
{
String host = "127.0.0.1";
int port = 18080;
new NettyClient().connect(host, port);
}
private void connect(String host, int port) throws Exception
{
try
{
Bootstrap b = new Bootstrap();
b.group(group)
.option(ChannelOption.TCP_NODELAY, true)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer()
{
@Override
protected void initChannel(SocketChannel ch) throws Exception
{
// NettyMessageDecoder设置了单条消息最大值1MB,可以防止消息过大导致的内存溢出或者畸形码流,引发解码错位或内存分配失败
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 f = b.connect(host, port).sync();
System.out.println("连接成功-----> " + host + ":" + port);
f.channel().closeFuture().sync();
}
finally
{
// 发起重连操作
executorService.execute(() -> {
try
{
TimeUnit.SECONDS.sleep(5);
connect(host, port);
}
catch (Exception e)
{
e.printStackTrace();
}
});
}
}
}
package com.xkl.learn.privateprotocol;
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 xiekunliang on 2018/2/24.
*/
public class NettyServer
{
public static void main(String[] args) throws Exception
{
new NettyServer().bind();
}
public void bind() throws Exception
{
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
try
{
ServerBootstrap b = new ServerBootstrap();
b.group(boss, work)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer()
{
@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(new ReadTimeoutHandler(50));
ch.pipeline().addLast(new LoginAuthRespHandler());
ch.pipeline().addLast(new HeartBeatRespHandler());
}
});
ChannelFuture f = b.bind(18080).sync();
System.out.println("Netty Server start ok! post is 18080");
f.channel().closeFuture().sync();
}
finally
{
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
}
运行测试时要注意:
1、断开自动重连是否正常。
2、断连时,心跳是否停止?
如果没有停止可能原因是在Handler的exceptionCaught方法里关闭了ctx,而没有将异常信息透传过去。