Netty:我的小故事

本文讲述 Netty 入门相关的内容,以帮助自己了解 Netty 的组成和使用方法。

引言

我的韩国欧巴,让我从 JDK 的混杂的环境中诞生。workaround 过不予修复“陷阱(BUG,BUG..)”默认失败地,跳过不可使用的 IP_TOS 选项,放弃 AIO 这个鸡肋。这一切让我变得如此与众不同,要说特别之处,我行异步,事件驱动之道;内有性能加持,外有呼风唤雨之术法;以三大形态互换,三大法相容相生,三几数笔达万丈之渊。现在的互联网,非复吴下阿蒙,一变不知样。以前尚且可以 CRUDER,现在只能去学习我,然后用上 Spring WebFlux 再与微服务搞上。此外,还得我来搞个前所未有的话术,提提性能冲点业绩,换点银子讨好老婆,养养儿子,爽歪歪(欧巴,我**你) 。在爽之余,要知道有我曾经有孪生兄弟他叫 5阿发,不过早早出家不问世事了(我会继承你的遗志的)。。。

那,我们就从 Echo 与 Http 开始,入门 Netty 吧! 首先,我的韩国欧巴把我放在 https://netty.io 上,如果要见我那你得去下载页面找到我,然后把我配置到 项目 中。不然你用 Maven 帮助你把我从另外一个地方出来帮你也行,别忘了我的版本号是3个点。 二话不说,先来个我需要的几个小家伙的介绍: 如果要搞个自定义协议的服务器端,要知道这几个 Bigman ,大人物 skr,skr,skr。

  • EventLoopGroup:任务分组,最多可以使用两组(是的,你没看错)
  • ServerBootstrap:Server 运作组织类,指挥部
  • SocketChannel:工作频道
  • ChannelHandler/ChannelInboundHandler:提供各种事件处理 ServerBootstrap 需要指定什么呢?
  • group():指定 事件循环 组
  • channel():指定 SocketChannel 类
  • handler:也是指定事件(对于 第一个 EventLoopGroup)
  • childHandler:指定事件处理
  • option():链接选项
  • childOption():链接选项 来看一段代码就明白了:
package com.codimiracle.practice;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class EchoServer {
private int port;
private EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) {
EchoServer server = new EchoServer(3400);
server.start();
}
private void start() {
NioEventLoopGroup majorEventLoopGroup = new NioEventLoopGroup();
NioEventLoopGroup seniorEventLoopGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
serverBootstrap
.group(majorEventLoopGroup, seniorEventLoopGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new LoggingHandler(LogLevel.INFO))
.addLast(new EchoService());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture channelFuture = serverBootstrap.bind(this.port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
majorEventLoopGroup.shutdownGracefully();
seniorEventLoopGroup.shutdownGracefully();
}
}
}

Handler 代码:

package com.codimiracle.practice;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class EchoService extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 内部缓存写入
ctx.write(msg);
// 把缓存发出
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
  • EventLoopGroup:任务分组,最多一个
  • Bootstrap:连长
  • SocketChannel:是的还是它
  • ChannelHandler/ChannelInboundHandler:没错,也是它 如果要搞个自定义的客户端,需要知道这几个 小东西。 就换了指挥的人。
package com.codimiracle.practice;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class EchoClient {
private String host;
private int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void connect() {
NioEventLoopGroup clientEventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(clientEventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new LoggingHandler(LogLevel.INFO))
.addLast(new EchoServiceProxy());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
clientEventLoopGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
EchoClient client = new EchoClient("localhost", 3400);
client.connect();
}
}

Client Handler 代码:

package com.codimiracle.practice;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class EchoServiceProxy extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.wrappedBuffer("Hello world\n".getBytes()));
}
}

我的三个模式

  • 排队打饭(排队模式 OIO/BIO,后来放弃了)
  • 点单自领(NIO模式)
  • 包厢等吃(AIO模式,后来移除了) 要更换模式,只需要 把 EventLoopGroup,ServerSocketChannel 的对象换成对应前缀的对象即可(如 OioEventLoopGroup,OioSocketChannel/OioServerSocketChannel) NIO 实现: EventLoopGroup:如果通过源码追溯进去,实际上是一个特殊化的 ExecutorService(线程池),特殊在那里呢? EventLoop:看一看代码,可以发现 EventLoop 处理了选择器当前(selectNow() 方法)的选择策略 SocketChannel:对应的事件频道

我的三种 Reactor

Reactor更像是一种事件的派发机制,但这些派发机制与响应的 IO 模式相关联。

  • ThreadPreConnection:使用 new NioEventLoopGroup(1) 创建,即可使用单 Reactor 单线程模式
  • Reactor:使用 new NioEventLoopGroup() 创建,即可使用单 Reactor 多线程模式
  • Proactor:使用两个 NioEventLoopGroup() 对象,即可创建主从 Reactor 多线程模式