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 多线程模式