Visión de conjunto
Fui a los búferes de protocolo de Google y di una vuelta breve y finalmente regresé. Obtuve una comprensión general del uso de protobuf. ¡Ahora vuelvo a integrarme con Netty para ver qué fragante es!
Protobuf integrado de Netty
Se han escrito varias demostraciones de datos de transmisión de clientes y servidores de Netty, y tengo una comprensión clara de las rutinas de Netty: preste atención al uso de Handler en varios escenarios y reescriba el estado de cada canalización en ChannelInboundHandlerAdapter Métodos de devolución de llamada (channelRegistered, channelUnregistered, channelRead0, etc. ) ... No repetiré cómo se escriben aquí el cliente y el servidor.
Servidor:
package com.leolee.netty.sixthExample;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import java.net.InetSocketAddress;
/**
* @ClassName TestServer
* @Description: 基于protobuf的服务端
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestServer {
public static void main(String[] args) throws InterruptedException {
//定义线程组 EventLoopGroup为死循环
//boss线程组一直在接收客户端发起的请求,但是不对请求做处理,boss会将接收到的请i交给worker线程组来处理
//实际可以用一个线程组来做客户端的请求接收和处理两件事,但是不推荐
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//启动类定义
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
//子处理器,自定义处理器,服务端可以使用childHandler或者handler,handlerr对应接收线程组(bossGroup),childHandler对应处理线程组(workerGroup)
.handler(new LoggingHandler(LogLevel.INFO))//日志处理器
.childHandler(new TestServerInitializer());
//绑定监听端口
ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync();
//定义关闭监听
channelFuture.channel().closeFuture().sync();
} finally {
//Netty提供的优雅关闭
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.leolee.netty.sixthExample;
import com.leolee.protobuf.DataInfo;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
/**
* @ClassName TestServerInitializer
* @Description: TODO
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("protobufVarint32FrameDecoder", new ProtobufVarint32FrameDecoder());
pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));
pipeline.addLast("protobufVarint32LengthFieldPrepender", new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast("protobufEncoder", new ProtobufEncoder());
pipeline.addLast(new TestServerHandler());
}
}
package com.leolee.netty.sixthExample;
import com.leolee.protobuf.DataInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @ClassName TestServerHandler
* @Description: TODO
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestServerHandler extends SimpleChannelInboundHandler<DataInfo.Student> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DataInfo.Student msg) throws Exception {
System.out.println(msg.getName());
System.out.println(msg.getAge());
System.out.println(msg.getAddress());
}
}
Lo principal a tener en cuenta es el siguiente código en TestServerInitializer
pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));
Y los cambios en el tipo genérico de la clase heredada SimpleChannelInboundHandler de TestServerHandler :
public class TestServerHandler extends SimpleChannelInboundHandler<DataInfo.Student> {
}
Aquí utilicé mi aprendizaje en protobuf (3): compilar archivos .proto para generar código Java, y serializar y deserializar mensajes para generar código Java: clase DataInfo
Esta clase proporciona una serie de métodos de operación como la serialización y deserialización del mensaje del estudiante para ayudarnos a construir los datos requeridos para la transmisión de mensajes de Netty de manera extremadamente simple, y Netty también tiene un buen soporte para protobuf.
Cliente:
package com.leolee.netty.sixthExample;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* @ClassName TestClient
* @Description: 基于protobuf的客户端
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestClient {
public static void main(String[] args) throws InterruptedException {
//客户端只需要一个线程组
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
//声明客户端启动类
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new TestClientInitializer());
ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
channelFuture.channel().closeFuture().sync();
} finally {
//优雅关闭
eventLoopGroup.shutdownGracefully();
}
}
}
package com.leolee.netty.sixthExample;
import com.leolee.protobuf.DataInfo;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
/**
* @ClassName TestClientInitializer
* @Description: TODO
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("protobufVarint32FrameDecoder", new ProtobufVarint32FrameDecoder());
pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));
pipeline.addLast("protobufVarint32LengthFieldPrepender", new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast("protobufEncoder", new ProtobufEncoder());
pipeline.addLast("TestClientHandler", new TestClientHandler());
}
}
package com.leolee.netty.sixthExample;
import com.leolee.protobuf.DataInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @ClassName TestClientHandler
* @Description: TODO
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestClientHandler extends SimpleChannelInboundHandler<DataInfo.Student> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DataInfo.Student msg) throws Exception {
}
/**
* 功能描述: <br> 连接建立变为活跃状态后,马上向服务端写入Student message
* 〈〉
* @Param: [ctx]
* @Return: void
* @Author: LeoLee
* @Date: 2020/9/2 21:11
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
DataInfo.Student student = DataInfo.Student
.newBuilder()
.setName("LeoLee")
.setAge(25)
.setAddress("上海")
.build();
ctx.channel().writeAndFlush(student);
}
}
resultado de la operación:
九月 02, 2020 10:14:20 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x034b9076] REGISTERED
九月 02, 2020 10:14:20 下午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x034b9076] BIND: 0.0.0.0/0.0.0.0:8899
九月 02, 2020 10:14:20 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x034b9076, L:/0:0:0:0:0:0:0:0:8899] ACTIVE
九月 02, 2020 10:14:30 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x034b9076, L:/0:0:0:0:0:0:0:0:8899] READ: [id: 0x054993bd, L:/127.0.0.1:8899 - R:/127.0.0.1:49587]
九月 02, 2020 10:14:30 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0x034b9076, L:/0:0:0:0:0:0:0:0:8899] READ COMPLETE
LeoLee
25
上海
Entrega de tipo de mensaje múltiple
El ejemplo anterior demuestra el análisis y la serialización de protobuf integrado de Netty, pero DataInfo.Student.getDefaultInstance () y SimpleChannelInboundHandler <DataInfo.Student> son difíciles de escribir , por lo que existe un problema de que el cliente solo puede pasar DataInfo. Mensaje de tipo de estudiante , el escenario de aplicación real, la comunicación de datos del cliente y del servidor es definitivamente diversa, Netty no proporciona una función de enrutamiento de solicitudes similar a SpringMVC, de hecho, el cliente y el servidor establecen una comunicación mantenible Canal, todos los datos deben transmitirse a través de este canal, entonces, ¿cómo debemos lidiar con esta situación real?
Hay un tipo de campo en protobuf llamada oneof , que se explica en la página oficial:
Si desea establecer solo un valor en un mensaje de campos múltiples al mismo tiempo, puede usar la función oneof para resolver este problema y ahorrar memoria.
El campo Oneof es como otros campos modificados opcionales, excepto por la función de memoria compartida , y solo se puede configurar un campo Oneof al mismo tiempo . Al configurar cualquier campo miembro, oneof borrará otros campos . Los ejemplos son los siguientes:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
Así que creemos nuestro archivo .proto .
syntax = "proto2";
package com.leolee.protobuf;
option optimize_for = SPEED;//Can be set to SPEED, CODE_SIZE, or LITE_RUNTIME,This affects the C++ and Java code generators (and possibly third-party generators) in the following ways
option java_package = "com.leolee.protobuf";
option java_outer_classname = "DataInfo2";
//生成java code 命令:protoc --java_out=src/main/java/ src/protobuf/Person.proto
//----------------多message的根节点
message DataPackage {
optional PackageType package_type = 1;
oneof Package {
Student sudent = 2;
Dog dog = 3;
}
}
//数据包类型
enum PackageType {
STUDENT = 0;
DOG = 1;
}
//----------------多message
message Student {
optional string name = 1;
optional int32 age = 2;
optional string address = 3;
}
message Dog {
optional string dog_name = 1;
optional int32 dog_age = 2;
}
Utilice el comando para generar código Java:
protoc --java_out=src/main/java/ src/protobuf/Person.proto
Para obtener un tutorial sobre la generación de código de protobuf, consulte aquí: aprendizaje de protobuf (3): compile archivos .proto para generar código Java y serialice y deserialice mensajes
Modifique el código del cliente y del servidor antes
Cliente:
package com.leolee.netty.sixthExample.multiProtocol;
import com.leolee.protobuf.DataInfo;
import com.leolee.protobuf.DataInfo2;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
/**
* @ClassName TestClientInitializer
* @Description: TODO
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("protobufVarint32FrameDecoder", new ProtobufVarint32FrameDecoder());
pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo2.DataPackage.getDefaultInstance()));
pipeline.addLast("protobufVarint32LengthFieldPrepender", new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast("protobufEncoder", new ProtobufEncoder());
pipeline.addLast("TestClientHandler", new TestClientHandler());
}
}
package com.leolee.netty.sixthExample.multiProtocol;
import com.leolee.protobuf.DataInfo2;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @ClassName TestClientHandler
* @Description: TODO
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestClientHandler extends SimpleChannelInboundHandler<DataInfo2.DataPackage> {
private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
@Override
protected void channelRead0(ChannelHandlerContext ctx, DataInfo2.DataPackage msg) throws Exception {
}
/**
* 功能描述: <br> 连接建立变为活跃状态后,马上向服务端写入Student message
* 〈〉
* @Param: [ctx]
* @Return: void
* @Author: LeoLee
* @Date: 2020/9/2 21:11
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//一秒执行一次
executor.scheduleAtFixedRate(() -> {
//随机生成 0 或者 1
int packType = new Random().nextInt(2);
switch (DataInfo2.DataPackage.PackageType.forNumber(packType)) {
case STUDENT:
System.out.println("发送student");
DataInfo2.DataPackage dataPackage = DataInfo2.DataPackage.newBuilder()
.setPackageType(DataInfo2.DataPackage.PackageType.STUDENT)
.setSudent(DataInfo2.Student.newBuilder()
.setName("LeoLee").setAge(25).setAddress("上海").build()).build();
ctx.channel().writeAndFlush(dataPackage);
break;
case DOG:
System.out.println("发送dog");
DataInfo2.DataPackage dataPackage2 = DataInfo2.DataPackage.newBuilder()
.setPackageType(DataInfo2.DataPackage.PackageType.DOG)
.setDog(DataInfo2.Dog.newBuilder()
.setDogName("恶霸犬").setDogAge(3).build()).build();
ctx.channel().writeAndFlush(dataPackage2);
break;
}
}, 0, 1, TimeUnit.SECONDS);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("客户端出现异常已关闭");
cause.printStackTrace();
ctx.close();
}
}
Servidor:
package com.leolee.netty.sixthExample.multiProtocol;
import com.leolee.protobuf.DataInfo2;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
/**
* @ClassName TestServerInitializer
* @Description: TODO
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("protobufVarint32FrameDecoder", new ProtobufVarint32FrameDecoder());
pipeline.addLast("protobufDecoder", new ProtobufDecoder(DataInfo2.DataPackage.getDefaultInstance()));
pipeline.addLast("protobufVarint32LengthFieldPrepender", new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast("protobufEncoder", new ProtobufEncoder());
pipeline.addLast(new TestServerHandler());
}
}
package com.leolee.netty.sixthExample.multiProtocol;
import com.leolee.protobuf.DataInfo2;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @ClassName TestServerHandler
* @Description: TODO
* @Author LeoLee
* @Date 2020/9/2
* @Version V1.0
**/
public class TestServerHandler extends SimpleChannelInboundHandler<DataInfo2.DataPackage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DataInfo2.DataPackage msg) throws Exception {
System.out.println("msg.getPackageType().getNumber():" + msg.getPackageType().getNumber());
//msg.PackageType.forNumber(packType)
switch (msg.getPackageType().getNumber()) {
case 0:
System.out.println(msg.getSudent().getName());
System.out.println(msg.getSudent().getAge());
System.out.println(msg.getSudent().getAddress());
break;
case 1:
System.out.println(msg.getDog().getDogName());
System.out.println(msg.getDog().getDogAge());
break;
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("服务端出现异常已关闭");
cause.printStackTrace();
ctx.close();
}
}
Los resultados de la implementación no se publicarán, y siéntelos por ti mismo.
Aquellos que necesitan código vienen aquí para obtenerlo: dirección del proyecto de demostración
Continuará, ¿cuántos controladores integrados no explicaron?