O framework Netty simplifica a implementação de serivdores de rede de alto desempenho. O processo de inicialização envolve a configuração de componentes essenciais como pools de threads, canais e pipelines de processamento.
A inicialização começa com a instanciação de um ServerBootstrap, que serve como um guia para configuração dos parâmetros do servidor. Em seguida, dois pools de threads, representados por EventLoopGroup, são criados. Um gerencia a aceitação de conexões (geralmente chamado de "boss"), e o outro manipula as operações de I/O e a lógica de negócio dos canais conectados.
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup connectionAcceptor = new NioEventLoopGroup(1);
EventLoopGroup ioWorker = new NioEventLoopGroup();
O método group associa esses pools ao bootstrap. A classe do canal do servidor (NioServerSocketChannel) e um inicializador para os canais filhos (ChannelInitializer) são configurados encadeadamente.
bootstrap.group(connectionAcceptor, ioWorker)
.channel(NioServerSocketChannel.class)
.childHandler(new ProtocolHandlerInitializer());
O ProtocolHandlerInitializer é responsável por configurar o pipeline de cada nova conexão de cliente. O pipeline é uma cadeia de manipuladores (ChannelHandler) que processam os eventos de rede, como codificação/decodificação e lógica da aplicação.
public class ProtocolHandlerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel clientChannel) {
ChannelPipeline pipeline = clientChannel.pipeline();
pipeline.addLast("httpCodec", new HttpServerCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast("businessLogic", new WebSocketBusinessHandler());
}
}
O WebSocketBusinessHandler implementa a interface ChannelInboundHandler para definir o comportamento da aplicação. Ele inspeciona mensagens recebidas, como requisições HTTP ou quadros WebSocket, e gera respostas adequadas. Este é o ponto onde a lógica específica da aplicação é inserida.
public class WebSocketBusinessHandler extends SimpleChannelInboundHandler<Object> {
private WebSocketServerHandshaker handshaker;
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpRequest) {
processHttp((FullHttpRequest) msg, ctx);
} else if (msg instanceof WebSocketFrame) {
processWebSocket((WebSocketFrame) msg, ctx);
}
}
private void processHttp(FullHttpRequest request, ChannelHandlerContext ctx) {
// Lógica para handshake do WebSocket
String wsUrl = "ws://localhost:" + 8080 + "/ws-endpoint";
WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(wsUrl, null, true);
this.handshaker = factory.newHandshaker(request);
if (this.handshaker == null) {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
} else {
this.handshaker.handshake(ctx.channel(), request);
}
}
private void processWebSocket(WebSocketFrame frame, ChannelHandlerContext ctx) {
if (frame instanceof CloseWebSocketFrame) {
this.handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
return;
}
if (frame instanceof TextWebSocketFrame) {
String receivedText = ((TextWebSocketFrame) frame).text();
String response = receivedText + ", bem-vindo ao servidor WebSocket!";
ctx.channel().write(new TextWebSocketFrame(response));
}
}
}
Finalmente, após toda a configuração, o servidor é efetivamente iniciado, vinculando-o a uma porta de rede. O bind inicia a escuta, e o mecanismo de Selector do NIO começa a monitorar os eventos de I/O, delegando-os para as respectivas EventLoop e seus pipelines de handlers para processamento.
Channel serverChannel = bootstrap.bind(8080).sync().channel();
serverChannel.closeFuture().sync();