MessageToByteEncoder
MessageToByteEncoder é um codificador abstrato e as subclasses podem substituir o método encode para codificar objetos na saída ByteBuf.
MessageToByteEncoder herda de ChannelOutboundHandlerAdapter, encode é chamado na saída.
public class MyMessageEncoder extends MessageToByteEncoder<MessagePO> {
@Override
protected void encode(ChannelHandlerContext ctx, MessagePO msg, ByteBuf out) throws Exception {
System.out.println("MyMessageEncoder.encode,被调用");
String json = JSONObject.toJSONString(msg);
out.writeInt(json.getBytes(StandardCharsets.UTF_8).length);
out.writeBytes(json.getBytes(StandardCharsets.UTF_8));
}
}
ByteToMessageDecoder
ByteToMessageDecoder é um ChannelInboundHandler, que pode ser chamado de decodificador, e é responsável por converter o fluxo de bytes (ByteBuf) em uma Message.Message é um objeto Java que a própria aplicação pode definir.
ByteToMessageDecoder: Utilizado para converter bytes em mensagens, é necessário verificar se há bytes suficientes no buffer.
public class MyMessageDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyMessageDecoder.decode,被调用");
while (in.readableBytes() >= 4){
int num = in.readInt();
System.out.println("解码出一个整数:"+num);
out.add(num);
}
}
}
ReplayingDecoder
ReplayingDecoder: Herdado de ByteToMessageDecoder, não há necessidade de verificar se há bytes suficientes no buffer, mas ReplayingDecoder é um pouco mais lento que ByteToMessageDecoder e nem todos os ByteBufs o suportam.
Use ReplayingDecoder se a complexidade do projeto for alta, caso contrário, use ByteToMessageDecoder.
public class MyMessageDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyMessageDecoder.decode,被调用");
int length = in.readInt();
byte[] content = new byte[length];
in.readBytes(content);
String json = new String(content,StandardCharsets.UTF_8);
MessagePO po = JSONObject.parseObject(json,MessagePO.class);
out.add(po);
}
}
MessageToMessageEncoder
Usado para codificar de uma mensagem para outra, como de POJO para POJO, é um ChannelOutboundHandler
MessageToMessageDecoder
A decodificação de uma mensagem para outra, como POJO para POJO, é um ChannelInboundHandler
MessageToMessageCodec
MessageToMessageEncoder e MessageToMessageDecoder integrados
public class RequestMessageCodec extends MessageToMessageCodec<String, RequestData> {
@Override
protected void encode(ChannelHandlerContext ctx, RequestData msg, List<Object> out) throws Exception {
System.out.println("RequestMessageCodec.encode 被调用 " + msg);
String json = JSONObject.toJSONString(msg);
out.add(json);
}
@Override
protected void decode(ChannelHandlerContext ctx, String msg, List<Object> out) throws Exception {
System.out.println("RequestMessageCodec.decode 被调用 " + msg);
RequestData po = JSONObject.parseObject(msg, RequestData.class);
out.add(po);
}
}
ChannelInitializer
ChannelInitializer é um ChannelInboundHandler especial que pode inicializar o Canal de forma simples (chamando o método initChannel).
Normalmente defina ChannelPipeline para Channel in Bootstrap.handler(ChannelHandler)
, eServerBootstrap.handler(ChannelHandler)
.ServerBootstrap.childHandler(ChannelHandler)
Nota: Quando o initChannel for executado, o manipulador atual será removido do Pipeline.
Bootstrap bootstrap = new Bootstrap().group(group)//设置线程组
.channel(NioSocketChannel.class)//设置客户端通道的实现类
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler());//加入自己的处理器
}
});
ServerBootstrap bootstrap = new ServerBootstrap().group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)//使用NioServerSocketChannel作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG, 128)//设置线程队列等待连接的个数
.childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持活动连接状态
// .handler(null)//该Handler对应bossGroup
.childHandler(new ChannelInitializer<SocketChannel>() {
//给workerGroup的EventLoop对应的管道设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyServerHandler());
}
});
SimpleChannelInboundHandler
SimpleChannelInboundHandler herda de ChannelInboundHandlerAdapter e pode especificar o tipo de mensagem por meio de genéricos.
Para processar os dados recebidos, precisamos apenas implementar o método channelRead0.
SimpleChannelInboundHandler liberará automaticamente o recurso Bytebuffer ocupado pelos dados após receber os dados, enquanto ChannelInboundHandlerAdapter não o liberará automaticamente.
public class MyClientHandler extends SimpleChannelInboundHandler<MessagePO> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, MessagePO msg) throws Exception {
System.out.println("收到服务端消息:" + msg);
}
}
DefaultEventLoopGroup
Ao adicionar um ChannelHandler ao pipline, um novo grupo de threads pode ser fornecido e o negócio do Handler será executado neste thread.
DefaultEventLoopGroup pode ser útil ao adicionar ChannelHandler para executar negócios simultâneos multiencadeados.
Se DefaultEventLoopGroup não estiver definido, o padrão éEventLoopGroup workerGroup = new NioEventLoopGroup();
DefaultEventLoopGroup businessGroup = new DefaultEventLoopGroup(100);
...
addLast(businessGroup, new MyNettyServerHandler())
/**
* 读取客户端发送过来的消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("收到客户信息:" + byteBuf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:" + ctx.channel().remoteAddress());
System.out.println("处理线程:" + Thread.currentThread().getName());
ctx.executor().parent().execute(()->{
try {
System.out.println("parent().execute Thread = " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2L);
System.out.println("parent任务执行完成1");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
ctx.executor().parent().execute(()->{
try {
System.out.println("parent().execute Thread = " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2L);
System.out.println("parent任务执行完成2");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
ctx.executor().parent().execute(()->{
try {
System.out.println("parent().execute Thread = " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2L);
System.out.println("parent任务执行完成3");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
O log de execução do código acima é o seguinte:
收到客户信息:Hello 服务端
客户端地址:/127.0.0.1:60345
处理线程:defaultEventLoopGroup-4-1
parent().execute Thread = defaultEventLoopGroup-4-2
parent().execute Thread = defaultEventLoopGroup-4-3
程序继续~~ defaultEventLoopGroup-4-1
parent().execute Thread = defaultEventLoopGroup-4-4
parent任务执行完成1
parent任务执行完成3
parent任务执行完成2
Tarefa cronometrada EventLoop
ctx.channel().eventLoop().schedule()
Tarefas cronometradas podem ser adicionadas por meio de métodos no Handler
ctx.channel().eventLoop().schedule(()->{
try {
System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2L);
System.out.println("定时任务执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
},10L,TimeUnit.SECONDS);