参考Netty权威指南:Netty4版本的私有协议栈

1、pom文件

 
        
            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;
    }

}

业务消息:编码Handler

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 out) throws Exception
    {
        if (msg == null || msg.getHeader() == null)
        {
            throw new Exception("The encode message is null");
        }
        ByteBuf sendBuf = Unpooled.buffer();
        sendBuf.writeInt(msg.getHeader().getCrcCode());
        sendBuf.writeInt(msg.getHeader().getLength());
        sendBuf.writeLong(msg.getHeader().getSessionID());
        sendBuf.writeByte(msg.getHeader().getType());
        sendBuf.writeByte(msg.getHeader().getPriority());
        sendBuf.writeInt(msg.getHeader().getAttachment().size());
        for (Map.Entry entry : msg.getHeader().getAttachment().entrySet())
        {
            String key = entry.getKey();
            byte[] keyArray = key.getBytes("UTF-8");
            sendBuf.writeInt(keyArray.length);
            sendBuf.writeBytes(keyArray);
            Object value = entry.getValue();
            nettyMarshallingEncoder.encode(ctx, value, sendBuf);
        }

        if (msg.getBody() != null)
        {
            nettyMarshallingEncoder.encode(ctx, msg.getBody(), sendBuf);
        }
        sendBuf.setInt(4, sendBuf.readableBytes());
        out.add(sendBuf);
    }

}


业务消息:解码Handler

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;

    }

}


业务消息pojo

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<>();
}

MessageType类

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;
}

Client : 握手请求 LoginAuthReqHandler

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;
    }
}


Server: 握手应答LoginAuthRespHandler

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");
        }

    }
}


Client: 心跳请求HeartBeatReqHandler

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;
    }

}

Server: 心跳应答HeartBeatRespHandler

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;
    }
}

Client类

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();
                }
            });
        }

    }
}


Server类

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,而没有将异常信息透传过去。

你可能感兴趣的:(Netty)