Netty (5) - defines the communication protocol from the codec packet

A. What is the communication protocol

This refers to the communication protocol is not narrowly TCP, UDP [such] a standard communication protocol, but refers Netty for interactive data between client and server communication protocol [Custom]. Whether using Netty or original Socket programming, TCP packet-based communications are binary format, protocol refers to the client and the server in advance to discuss good, each binary byte packets in each section represent what meaning rule. As shown in a simple login protocol used follows:
Here Insert Picture Description

In this packet, a first 1 byte indicates that this is a login command, followed by the user name and password, these two values ​​\ 0 split, the client sends this binary data packet to the server, the server You will be able to remove the user name and password Under the agreement, log logic.
Here Insert Picture Description
How to design a communication protocol package?

  1. First, the first field is a magic number, usually a fixed number of bytes (which we here defined as 4 bytes). Why do we need this field, but also a fixed number? Suppose we had a port on the server, such as port 80, if there is no magic number, any packets transmitted to the server, the server will be based on custom protocols are processed, including custom protocols do not meet the specification of the packet. For example, we directly through http: // ip server to access the server (default port 80), the server receives a standard HTTP protocol packets, but it will still be in accordance with the protocol agreed in advance to handle the HTTP protocol, obviously, this will resolve the error. With this, after the magic number, the server first removed to compare the first four bytes, can recognize this packet is not the first time following the custom protocols, i.e., invalid data packets, for safety considerations direct Close the connection to save resources. In Java bytecode binary file, the beginning of the 4 bytes 0xcafebabe used to identify this is a byte code file, also the same purpose.

  2. The next byte is the version number, usually a reserved field for the protocol used when the upgrade, somewhat similar to a field identifies the TCP protocol is IPV4 or IPV6 protocol agreement, in most cases, this field is less than, but the agreement in order to support the upgrade, we first have it.

  3. The third part shows how the serialization algorithm converts the binary data to Java objects and how to convert the binary data back to Java objects, such as built-in Java serialization, json, hessian and other serialization.

  4. The fourth field indicates the portion of the instruction, the instruction on the introduction relevant, we have already been discussed earlier that, for each service or client receives an instruction-processing logic will have a corresponding, here, we use one byte, the maximum supports 256 instructions for our IM system is already completely sufficient.

  5. The next field is the length of the data portion, representing four bytes.

  6. The last part of the data content, each corresponding to the command data is not the same, such as when a login username and password required, the received message when the message requires a user ID and the specific content and the like.

Here Insert Picture Description

II. Code implementation

  • Project Directory

Here Insert Picture Description

  • utils/command
/*
 * @Author ARong
 * @Description 定义指令集
 * @Date 2020/2/4 8:58 下午
 **/
public interface Command {
    byte LOGIN = 1;
}
  • utils/MySerializer

/*
 * @Author ARong
 * @Description 定义序列化器
 * @Date 2020/2/4 9:06 下午
 **/
public interface MySerializer {

    /*
     * @Author ARong
     * @Description 将Java对象转化为二进制字节流
     * @Date 2020/2/4 9:07 下午
     * @Param object
     * @return byte[]
     **/
    byte[] serialize(Object object);


    /*
     * @Author ARong
     * @Description 二进制转换成 java 对象
     * @Date 2020/2/4 9:08 下午
     * @Param [clazz, bytes]
     * @return T
     **/
    <T> T deserialize(Class<T> clazz, byte[] bytes);
}
  • utils/Serialize
/*
 * @Author ARong
 * @Description 定义序列化方式
 * @Date 2020/2/4 9:05 下午
 **/
public interface Serialize {
    byte JSON = 1;
}
  • utils/SerializerFactory
/*
 * @Author ARong
 * @Description 序列化器工厂
 * @Date 2020/2/4 8:56 下午
 **/
public class SerializerFactory {
    /*
     * @Author ARong
     * @Description 通过序列化名称获取相应的序列化器
     * @Date 2020/2/4 9:17 下午
     * @Param [serName]
     * @return io_learn.netty.part4_protocol.utils.MySerializer
     **/
    public static MySerializer getSerializer(byte serName) {
        if (serName == Serialize.JSON) {
            return new MyJsonSerializer();
        }
        return null;
    }
}

  • utils/MyJsonSerializer
/**
 * @Auther: ARong
 * @Date: 2020/2/4 9:09 下午
 * @Description:
 */
public class MyJsonSerializer implements MySerializer {
    /*
     * @Author ARong
     * @Description 将Java对象转化为二进制字节流
     * @Date 2020/2/4 9:07 下午
     * @Param object
     * @return byte[]
     **/
    @Override
    public byte[] serialize(Object object) {
        return JSON.toJSONBytes(object);
    }

    /*
     * @Author ARong
     * @Description 二进制转换成 java 对象
     * @Date 2020/2/4 9:08 下午
     * @Param [clazz, bytes]
     * @return T
     **/
    @Override
    public <T> T deserialize(Class<T> clazz, byte[] bytes) {
        return JSON.parseObject(bytes, clazz);
    }
}
  • utils/PacketUtil
/**
 * @Auther: ARong
 * @Date: 2020/2/4 9:14 下午
 * @Description: 用于包编码和解码
 */
public class PacketUtil {
    /*
     * @Author ARong
     * @Description 将Packet按照约定序列化方式编码成ByteBuf
     * @Date 2020/2/4 9:15 下午
     * @Param [packet]
     * @return io.netty.buffer.ByteBuf
     **/
    public static ByteBuf encode(Packet packet) {
        // 创建ByteBuf对象
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();
        // 获取序列化器序列化对象
        MySerializer serializer = SerializerFactory.getSerializer(packet.getSerMethod());
        byte[] data = serializer.serialize(packet);
        // 按照通信协议填充ByteBUf
        byteBuf.writeInt(packet.getMagic());// 魔数
        byteBuf.writeByte(packet.getVersion()); // 版本号
        byteBuf.writeByte(packet.getSerMethod()); // 序列化方式
        byteBuf.writeByte(packet.getCommand()); // 指令
        byteBuf.writeInt(data.length);// 数据长度
        byteBuf.writeBytes(data); // 数据
        return byteBuf;
    }

    /*
     * @Author ARong
     * @Description 将ByteBuf按照约定序列化方式解码成Packet
     * @Date 2020/2/4 9:21 下午
     * @Param [byteBuf]
     * @return io_learn.netty.part4_protocol.packet.Packet
     **/
    public static Packet decode(ByteBuf byteBuf) {
        // 暂不判断魔数,跳过
        byteBuf.skipBytes(4);
        // 暂不判断魔数版本号,跳过
        byteBuf.skipBytes(1);
        // 获取序列化方式
        byte serMethod = byteBuf.readByte();
        // 获取指令
        byte command = byteBuf.readByte();
        // 获取数据包长度
        int length = byteBuf.readInt();
        // 获取存储数据的字节数组
        byte[] data = new byte[length];
        byteBuf.readBytes(data);
        // 反序列化数据,获取Packet
        Class<? extends Packet> packetType = getPacketType(command);
        Packet res = SerializerFactory.getSerializer(serMethod).deserialize(packetType, data);
        return res;
    }


    /*
     * @Author ARong
     * @Description 通过指令获取相应的Packet
     * @Date 2020/2/4 9:31 下午
     * @Param [commond]
     * @return io_learn.netty.part4_protocol.packet.Packet
     **/
    public static Class<? extends Packet> getPacketType(byte commond) {
        if (commond == Command.LOGIN) {
            return LoginPacket.class;
        }
        return null;
    }
}

  • packet/Packet
/**
 * @Auther: ARong
 * @Date: 2020/2/4 8:44 下午
 * @Description: 自定义通信协议的抽象包
 */
@Data
public abstract class Packet {
    private int magic; // 魔数
    private byte version; // 版本号
    private byte serMethod; // 的序列化/反序列化方式
    private byte command; // 指令
}
  • packet/LoginPacket
/**
 * @Auther: ARong
 * @Date: 2020/2/4 8:52 下午
 * @Description: 用以登陆的登陆协议包
 */
@Data
public class LoginPacket extends Packet {
    private int magic = 20202020; // 魔数
    private byte version = 1; // 版本号
    private byte serMethod = Serialize.JSON; // 序列化反序列化方式为Json
    private byte command = Command.LOGIN; // 登陆指令
    // ---------以下为登陆数据----------
    private long userId;
    private String name;
    private String password;


    @Override
    public String toString() {
        return "LoginPacket{" +
                "magic=" + magic +
                ", version=" + version +
                ", serMethod=" + serMethod +
                ", command=" + command +
                ", userId=" + userId +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
  • server/NettyServer
/**
 * @Auther: ARong
 * @Date: 2020/2/4 9:13 下午
 * @Description: Netty Server
 */
public class NettyServer {
    public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(
                        new ChannelInitializer<SocketChannel>() {
                            @Override
                            // 初始化channel
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new MyServerHandler());
                            }
                        }
                ).bind(8000);
    }
}

  • server/NettyClient
/**
 * @Auther: ARong
 * @Date: 2020/2/5 1:28 下午
 * @Description: NettyClient
 */
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup group = new NioEventLoopGroup();

        bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                // 责任链模式,添加第一次连接的客户端处理逻辑
//                ch.pipeline().addLast(new FirstClientHandler());
            }
        });

        Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
        LoginPacket packet = new LoginPacket();
        packet.setUserId(123l);
        packet.setName("ARong");
        packet.setPassword("123");
        ByteBuf byteBuf = PacketUtil.encode(packet);
        System.out.println("客户端正在发送包");
        channel.writeAndFlush(byteBuf);
    }
}

  • handler/MyServerHandler
/**
 * @Auther: ARong
 * @Date: 2020/2/5 1:17 下午
 * @Description:
 */
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务端接收到消息,正在解码");
        Packet packet = PacketUtil.decode((ByteBuf) msg);
        if (packet.getCommand() == Command.LOGIN) {
            packet = (LoginPacket)packet;
        }
        System.out.println(packet);
    }
}

Here Insert Picture Description

Published 309 original articles · won praise 205 · Views 300,000 +

Guess you like

Origin blog.csdn.net/pbrlovejava/article/details/104186101