Netty (5) - define el protocolo de comunicación del paquete de codec

A. ¿Cuál es el protocolo de comunicación

Esto se refiere al protocolo de comunicación no es estrechamente TCP, UDP [como] un protocolo de comunicación estándar, sino que se refiere Netty de datos interactivos entre el protocolo de comunicaciones cliente y el servidor [Personalizado]. Si el uso de Netty o la programación del zócalo original, comunicaciones basadas en paquetes TCP son formato binario, el protocolo se refiere al cliente y el servidor de antemano para discutir bueno, cada paquete de datos binarios en cada sección representan lo significado reglas. Como se muestra en un protocolo de inicio de sesión simple que se usa de la siguiente manera:
Aquí Insertar imagen Descripción

En este paquete, un primer 1 byte indica que se trata de un comando de inicio de sesión, seguido del nombre de usuario y contraseña, estos dos valores 0 \ dividida, el cliente envía este paquete de datos binarios al servidor, el servidor usted será capaz de eliminar el nombre de usuario y contraseña Según el acuerdo, la lógica de registro.
Aquí Insertar imagen Descripción
Cómo diseñar un paquete de protocolo de comunicación?

  1. En primer lugar, el primer campo es un número mágico, por lo general un número fijo de bytes (que aquí hemos definido como 4 bytes). ¿Por qué necesitamos este campo, sino también un número fijo? Supongamos que tenemos un puerto en el servidor, como el puerto 80, si no hay un número mágico, todos los paquetes transmitidos al servidor, el servidor se basa en protocolos personalizados se procesan, incluyendo protocolos personalizados no cumplen con la especificación del paquete. Por ejemplo, directamente a través de http: // ip servidor para acceder al servidor (puerto por defecto 80), el servidor recibe una paquetes de protocolo HTTP estándar, pero todavía será de acuerdo con el protocolo acordado por adelantado para manejar el protocolo HTTP, obviamente, esto va a resolver el error. Con esto, después de que el número mágico, el servidor elimina en primer lugar para comparar los primeros cuatro bytes, puede reconocer este paquete no es la primera vez siguiendo los protocolos personalizados, es decir, los paquetes de datos no válidos, por consideraciones de seguridad directa cerrar la conexión para ahorrar recursos. En Java bytecode archivo binario, el comienzo de los 4 bytes 0xCAFEBABE utiliza para identificar este es un archivo de código de bytes, también con el mismo propósito.

  2. El siguiente byte es el número de versión, por lo general un campo reservado para el protocolo que se utiliza cuando la actualización, algo similar a un campo identifica el protocolo TCP es IPv4 o IPv6 protocolo de acuerdo, en la mayoría de los casos, este campo es menos, pero el acuerdo con el fin de apoyar la actualización, primero tenemos que.

  3. La tercera parte muestra cómo el algoritmo de serialización convierte los datos binarios a objetos Java y cómo convertir la parte trasera de datos binarios a objetos Java, como una función de la serialización de Java, JSON, arpillera y otra serialización.

  4. El cuarto campo indica la parte de la instrucción, la instrucción relativa a la aplicación correspondiente, ya hemos sido discutido anteriormente que, para cada servicio o el cliente recibe una lógica de instrucciones de procesamiento tendrá un correspondiente, aquí, se utiliza un byte, el máximo 256 es compatible con instrucciones para nuestro sistema de mensajería instantánea ya es completamente suficiente.

  5. El siguiente campo es la longitud de la porción de datos, en representación de cuatro bytes.

  6. La última parte del contenido de datos, cada uno correspondiente a los datos del comando no es el mismo, como cuando requiere un nombre de usuario y la contraseña de inicio de sesión, el mensaje recibido cuando el mensaje requiere un ID de usuario y el contenido específico y similares.

Aquí Insertar imagen Descripción

II. La implementación del Código

  • Directorio de Proyectos

Aquí Insertar imagen Descripción

  • utils / comando
/*
 * @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;
    }
}

  • paquete / paquetes
/**
 * @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; // 指令
}
  • paquete / 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 + '\'' +
                '}';
    }
}
  • servidor / 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);
    }
}

  • servidor / 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);
    }
}

  • manejador / 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);
    }
}

Aquí Insertar imagen Descripción

Publicados 309 artículos originales · ganado elogios 205 · Vistas de 300.000 +

Supongo que te gusta

Origin blog.csdn.net/pbrlovejava/article/details/104186101
Recomendado
Clasificación