实现功能:
基于Netty的NIO通信框架,提供高性能的异步通信能力;
提供消息的编解码框架,可以实现POJO的序列化和反序列化;
提供基于IP地址的白名单接入认证机制;
链路的有效性校验机制;
链路的断连重连机制。
代码实现
相关vo:
消息的类型定义
/**
* @author yun
* 类说明:消息的类型定义
*/
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;
}
}
消息头
/**
* @author yun
* 类说明:消息头
*/
public final class MyHeader {
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>(); // 附件
public final int getCrcCode() {
return crcCode;
}
public final void setCrcCode(int crcCode) {
this.crcCode = crcCode;
}
public final int getLength() {
return length;
}
public final void setLength(int length) {
this.length = length;
}
public final long getSessionID() {
return sessionID;
}
public final void setSessionID(long sessionID) {
this.sessionID = sessionID;
}
public final byte getType() {
return type;
}
public final void setType(byte type) {
this.type = type;
}
public final byte getPriority() {
return priority;
}
public final void setPriority(byte priority) {
this.priority = priority;
}
public final Map<String, Object> getAttachment() {
return attachment;
}
public final void setAttachment(Map<String, Object> attachment) {
this.attachment = attachment;
}
@Override
public String toString() {
return "MyHeader [crcCode=" + crcCode + ", length=" + length
+ ", sessionID=" + sessionID + ", type=" + type + ", priority="
+ priority + ", attachment=" + attachment + "]";
}
}
消息实体类
/**
* @author yun
* 类说明:消息实体类
*/
public final class MyMessage {
private MyHeader myHeader;
private Object body;
public final MyHeader getMyHeader() {
return myHeader;
}
public final void setMyHeader(MyHeader myHeader) {
this.myHeader = myHeader;
}
public final Object getBody() {
return body;
}
public final void setBody(Object body) {
this.body = body;
}
@Override
public String toString() {
return "MyMessage [myHeader=" + myHeader + "][body="+body+"]";
}
}
常量说明
/**
* @author yun
* 类说明:常量说明
*/
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";
}
kryo序列相关实体:
反序列化的Handler
/**
* @author yun
* 类说明:反序列化的Handler
*/
public class KryoDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) throws Exception {
Object obj = KryoSerializer.deserialize(in);
out.add(obj);
}
}
序列化的Handler
/**
* @author yun
* 类说明:序列化的Handler
*/
public class KryoEncoder extends MessageToByteEncoder<MyMessage> {
@Override
protected void encode(ChannelHandlerContext ctx, MyMessage message,
ByteBuf out) throws Exception {
KryoSerializer.serialize(message, out);
ctx.flush();
}
}
Kryo的序列化工厂
/**
* @author yun
* 类说明:Kryo的序列化工厂
*/
public class KryoFactory {
public static Kryo createKryo() {
Kryo kryo = new Kryo();
kryo.setRegistrationRequired(false);
kryo.register(Arrays.asList("").getClass(), new ArraysAsListSerializer());
kryo.register(GregorianCalendar.class, new GregorianCalendarSerializer());
kryo.register(InvocationHandler.class, new JdkProxySerializer());
kryo.register(BigDecimal.class, new DefaultSerializers.BigDecimalSerializer());
kryo.register(BigInteger.class, new DefaultSerializers.BigIntegerSerializer());
kryo.register(Pattern.class, new RegexSerializer());
kryo.register(BitSet.class, new BitSetSerializer());
kryo.register(URI.class, new URISerializer());
kryo.register(UUID.class, new UUIDSerializer());
UnmodifiableCollectionsSerializer.registerSerializers(kryo);
SynchronizedCollectionsSerializer.registerSerializers(kryo);
kryo.register(HashMap.class);
kryo.register(ArrayList.class);
kryo.register(LinkedList.class);
kryo.register(HashSet.class);
kryo.register(TreeSet.class);
kryo.register(Hashtable.class);
kryo.register(Date.class);
kryo.register(Calendar.class);
kryo.register(ConcurrentHashMap.class);
kryo.register(SimpleDateFormat.class);
kryo.register(GregorianCalendar.class);
kryo.register(Vector.class);
kryo.register(BitSet.class);
kryo.register(StringBuffer.class);
kryo.register(StringBuilder.class);
kryo.register(Object.class);
kryo.register(Object[].class);
kryo.register(String[].class);
kryo.register(byte[].class);
kryo.register(char[].class);
kryo.register(int[].class);
kryo.register(float[].class);
kryo.register(double[].class);
return kryo;
}
}
kryo序列化类
/**
* @author yun
* 类说明:
*/
public class KryoSerializer {
private static Kryo kryo = KryoFactory.createKryo();
public static void serialize(Object object, ByteBuf out) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos);
kryo.writeClassAndObject(output, object);
output.flush();
output.close();
byte[] b = baos.toByteArray();
try {
baos.flush();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
out.writeBytes(b);
}
public static Object deserialize(ByteBuf out) {
if (out == null) {
return null;
}
Input input = new Input(new ByteBufInputStream(out));
return kryo.readClassAndObject(input);
}
}
客户端
业务启动类
/**
* @author hyun
* @date 2020年10月18日 下午5:55:41
*
*/
public class BusiClient {
public static void main(String[] args) throws Exception {
NettyClient nettyClient = new NettyClient();
new Thread(nettyClient).start();
while(!nettyClient.isConnected()) {
synchronized(nettyClient) {
nettyClient.wait();
}
}
System.out.println("网络通信已准备好,可以进行业务操作了。。。。。。");
Scanner scanner = new Scanner(System.in);
while(true) {
String msg = scanner.next();
if (msg == null) {
continue;
} else if("q".equals(msg.toLowerCase())) {
nettyClient.close();
while(nettyClient.isConnected()) {
synchronized(nettyClient) {
nettyClient.wait();
}
}
scanner.close();
System.exit(1);
} else {
nettyClient.send(msg);
}
}
}
}
客户端
/**
* @author yun
* 类说明:客户端的主入口
*/
public class NettyClient implements Runnable {
private static final Log LOG = LogFactory.getLog(NettyClient.class);
private ScheduledExecutorService executor = Executors
.newScheduledThreadPool(1);
private Channel channel;
private volatile boolean userClose = false;
private volatile boolean connected = false;
private EventLoopGroup group = new NioEventLoopGroup();
public boolean isConnected() {
return connected;
}
public void connect(int port, String host) throws Exception {
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ClientInit());
// 发起异步连接操作
ChannelFuture future = b.connect(
new InetSocketAddress(host, port),
new InetSocketAddress(NettyConstant.LOCALIP,
NettyConstant.LOCAL_PORT)).sync();
channel = future.sync().channel();
synchronized(this) {
this.connected = true;
this.notifyAll();
}
future.channel().closeFuture().sync();
} finally {
if(!userClose){
//再次发起重连操作
executor.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
try {
// 发起重连操作
connect(NettyConstant.PORT, NettyConstant.REMOTEIP);
} catch (Exception e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
} else {/*用户主动关闭,释放资源*/
channel = null;
group.shutdownGracefully().sync();
synchronized(this) {
this.connected = false;
this.notifyAll();
}
}
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
NettyClient nettyClient = new NettyClient();
nettyClient.connect(NettyConstant.PORT, NettyConstant.REMOTEIP);
}
/*------------以下方法供业务方使用--------------------------*/
public void send(String message) {
if(channel==null||!channel.isActive()){
throw new IllegalStateException("和服务器未建立起有效连接!");
}
MyMessage msg = new MyMessage();
MyHeader myHeader = new MyHeader();
myHeader.setType(MessageType.SERVICE_REQ.value());
msg.setMyHeader(myHeader);
msg.setBody(message);
channel.writeAndFlush(msg);
}
public void close() {
userClose = true;
channel.close();
}
@Override
public void run() {
try {
connect(NettyConstant.PORT, NettyConstant.REMOTEIP);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端Handler的初始化
/**
* @author yun
* 类说明:客户端Handler的初始化
*/
public class ClientInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
/*剥离接收到的消息的长度字段,拿到实际的消息报文的字节数组*/
ch.pipeline().addLast("frameDecoder",
new LengthFieldBasedFrameDecoder(65535,
0,2,0,
2));
/*给发送出去的消息增加长度字段*/
ch.pipeline().addLast("frameEncoder",
new LengthFieldPrepender(2));
/*反序列化,将字节数组转换为消息实体*/
ch.pipeline().addLast(new KryoDecoder());
/*序列化,将消息实体转换为字节数组准备进行网络传输*/
ch.pipeline().addLast("MessageEncoder",
new KryoEncoder());
/*超时检测*/
ch.pipeline().addLast("readTimeoutHandler",
new ReadTimeoutHandler(10));
/*发出登录请求*/
ch.pipeline().addLast("LoginAuthHandler",
new LoginAuthReqHandler());
/*发出心跳请求*/
ch.pipeline().addLast("HeartBeatHandler",
new HeartBeatReqHandler());
}
}
心跳请求处理
/**
* @author yun
* 类说明:心跳请求处理
*/
public class HeartBeatReqHandler extends ChannelInboundHandlerAdapter {
private static final Log LOG = LogFactory.getLog(HeartBeatReqHandler.class);
private volatile ScheduledFuture<?> heartBeat;
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
MyMessage message = (MyMessage) msg;
// 握手成功,主动发送心跳消息
if (message.getMyHeader() != null
&& message.getMyHeader().getType() == MessageType.LOGIN_RESP
.value()) {
heartBeat = ctx.executor().scheduleAtFixedRate(
new HeartBeatReqHandler.HeartBeatTask(ctx), 0, 5000,
TimeUnit.MILLISECONDS);
} else if (message.getMyHeader() != null
&& message.getMyHeader().getType() == MessageType.HEARTBEAT_RESP
.value()) {
// LOG.info("Client receive server heart beat message : ---> "
// + message);
ReferenceCountUtil.release(msg);
} else
ctx.fireChannelRead(msg);
}
private class HeartBeatTask implements Runnable {
private final ChannelHandlerContext ctx;
//心跳计数,可用可不用,已经有超时处理机制
private final AtomicInteger heartBeatCount;
public HeartBeatTask(final ChannelHandlerContext ctx) {
this.ctx = ctx;
heartBeatCount = new AtomicInteger(0);
}
@Override
public void run() {
MyMessage heatBeat = buildHeatBeat();
// LOG.info("Client send heart beat messsage to server : ---> "
// + heatBeat);
ctx.writeAndFlush(heatBeat);
}
private MyMessage buildHeatBeat() {
MyMessage message = new MyMessage();
MyHeader myHeader = new MyHeader();
myHeader.setType(MessageType.HEARTBEAT_REQ.value());
message.setMyHeader(myHeader);
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);
}
}
发起登录请求
/**
* @author yun
* 类说明:发起登录请求
*/
public class LoginAuthReqHandler extends ChannelInboundHandlerAdapter {
private static final Log LOG = LogFactory.getLog(LoginAuthReqHandler.class);
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buildLoginReq());
}
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
MyMessage message = (MyMessage) msg;
// 如果是握手应答消息,需要判断是否认证成功
if (message.getMyHeader() != null
&& message.getMyHeader().getType() == MessageType.LOGIN_RESP
.value()) {
byte loginResult = (byte) message.getBody();
if (loginResult != (byte) 0) {
// 握手失败,关闭连接
ctx.close();
} else {
LOG.info("Login is ok : " + message);
ctx.fireChannelRead(msg);
}
} else
ctx.fireChannelRead(msg);
}
private MyMessage buildLoginReq() {
MyMessage message = new MyMessage();
MyHeader myHeader = new MyHeader();
myHeader.setType(MessageType.LOGIN_REQ.value());
message.setMyHeader(myHeader);
return message;
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.fireExceptionCaught(cause);
}
}
服务端
服务端启动类
/**
* @author yun
* 类说明:服务端的主入口
*/
public class NettyServer {
private static final Log LOG = LogFactory.getLog(NettyServer.class);
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, 1024)
.childHandler(new ServerInit());
// 绑定端口,同步等待成功
b.bind(NettyConstant.REMOTEIP, NettyConstant.PORT).sync();
LOG.info("Netty server start : "
+ (NettyConstant.REMOTEIP + " : " + NettyConstant.PORT));
}
public static void main(String[] args) throws Exception {
new NettyServer().bind();
}
}
handler加载
/**
* @author yun
* 类说明:
*/
public class ServerInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
/*Netty提供的日志打印Handler,可以展示发送出去的字节*/
//ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
/*剥离接收到的消息的长度字段,拿到实际的消息报文的字节数组*/
ch.pipeline().addLast("frameDecoder",
new LengthFieldBasedFrameDecoder(65535,
0,2,0,
2));
/*给发送出去的消息增加长度字段*/
ch.pipeline().addLast("frameEncoder",
new LengthFieldPrepender(2));
/*反序列化,将字节数组转换为消息实体*/
ch.pipeline().addLast(new KryoDecoder());
/*序列化,将消息实体转换为字节数组准备进行网络传输*/
ch.pipeline().addLast("MessageEncoder",
new KryoEncoder());
/*超时检测*/
ch.pipeline().addLast("readTimeoutHandler",
new ReadTimeoutHandler(50));
/*登录应答*/
ch.pipeline().addLast(new LoginAuthRespHandler());
/*心跳应答*/
ch.pipeline().addLast("HeartBeatHandler",
new HeartBeatRespHandler());
/*服务端业务处理*/
ch.pipeline().addLast("ServerBusiHandler",
new ServerBusiHandler());
}
}
心跳
/**
* @author yun
* 类说明:心跳
*/
public class HeartBeatRespHandler extends ChannelInboundHandlerAdapter {
private static final Log LOG = LogFactory.getLog(HeartBeatRespHandler.class);
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
MyMessage message = (MyMessage) msg;
// 返回心跳应答消息
if (message.getMyHeader() != null
&& message.getMyHeader().getType() == MessageType.HEARTBEAT_REQ
.value()) {
// LOG.info("Receive client heart beat message : ---> "
// + message);
MyMessage heartBeat = buildHeatBeat();
// LOG.info("Send heart beat response message to client : ---> "
// + heartBeat);
ctx.writeAndFlush(heartBeat);
ReferenceCountUtil.release(msg);
} else
ctx.fireChannelRead(msg);
}
private MyMessage buildHeatBeat() {
MyMessage message = new MyMessage();
MyHeader myHeader = new MyHeader();
myHeader.setType(MessageType.HEARTBEAT_RESP.value());
message.setMyHeader(myHeader);
return message;
}
}
登录检查
/**
* @author yun
* 类说明:登录检查
*/
public class LoginAuthRespHandler extends ChannelInboundHandlerAdapter {
private final static Log LOG = LogFactory.getLog(LoginAuthRespHandler.class);
//用以检查用户是否重复登录的缓存
private Map<String, Boolean> nodeCheck = new ConcurrentHashMap<String, Boolean>();
//用户登录的白名单
private String[] whitekList = { "127.0.0.1"};
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
MyMessage message = (MyMessage) msg;
// 如果是握手请求消息,处理,其它消息透传
if (message.getMyHeader() != null
&& message.getMyHeader().getType() == MessageType.LOGIN_REQ
.value()) {
String nodeIndex = ctx.channel().remoteAddress().toString();
MyMessage 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;
}
}
loginResp = isOK ? buildResponse((byte) 0)
: buildResponse((byte) -1);
if (isOK)
nodeCheck.put(nodeIndex, true);
}
LOG.info("The login response is : " + loginResp
+ " body [" + loginResp.getBody() + "]");
ctx.writeAndFlush(loginResp);
ReferenceCountUtil.release(msg);
}
/*注释后,可演示消息不往下传递的情况*/
else {
ctx.fireChannelRead(msg);
}
}
private MyMessage buildResponse(byte result) {
MyMessage message = new MyMessage();
MyHeader myHeader = new MyHeader();
myHeader.setType(MessageType.LOGIN_RESP.value());
message.setMyHeader(myHeader);
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);
}
}
业务处理类
/**
* @author yun
* 类说明:业务处理类
*/
public class ServerBusiHandler extends SimpleChannelInboundHandler<MyMessage> {
private static final Log LOG = LogFactory.getLog(ServerBusiHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyMessage msg) throws Exception {
LOG.info(msg);
}
}