Netty 入门 — 亘古不变的Hello World

这篇文章我们正式开始学习 Netty,在入门之前我们还是需要了解什么是 Netty。

什么是 Netty

为什么很多人都推崇 Java boy 去研究 Netty?Netty 这么高大上,它到底是何方神圣?

用官方的话说:Netty 是一款异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序

为什么要使用 Netty 呢?因为使用原生的 Java NIO 非常不爽,它存在一系列的问题,比如:

  1. 使用 Java NIO 需要了解很多概念,而且 API 非常繁琐。
  2. 使用 Java NIO 编程复杂,一不小心就会 Bug 横飞。
  3. 开发工作量和难度也很大,例如我们要处理断开重连、网络闪断、半包读写、网络拥塞、异常处理,等等异常情况,处理起来难度比较大。需要非常熟悉 Java 多线程和网络相关知识点,才能编写一个高质量的 Java NIO 程序。
  4. JDK NIO 的 Bug:例如臭名昭著的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。

使用 Netty,都解决了上面的问题,它具备如下几个优点:

  1. 容易上手:Netty 是 Java NIO 进行了封装,API 使用简单,开发门槛相对较低。
  2. 功能强大:预置多种编解码功能,支持多种主流协议,底层 IO 模型随意切换等等。
  3. 高性能:精心设计的 Reactor 线程模型支持高并发,优秀的内存模型,减少了资源的消耗。
  4. 设计优雅:灵活且可扩展的事件模型,可以清晰地分离关注点;高度可定制化的线程模型;粘包/粘包、异常检测等机制将我们从繁重的细节中解脱,只需关注业务逻辑。
  5. 社区活跃:Netty 是目前最为活跃的开源项目之一,版本迭代周期短,bug 修复速度快。
  6. 安全:完整的 SSL/TLS 和 StartTLS 支持。
  7. 质量有保障:经过大规模的商业应用考验,质量得到了充分的验证,健壮性无比强大,放心使用吧。

Hello World

接下来我们用 Netty 来实现我们的第一个应用程序:Hello World。该 Hello World 程序大明哥做到尽可能简单,理解不了没有关系,能跑起来就行,毕竟是第一个 demo。

注:大明哥写这篇文章时, Netty 最新版本为:4.1.77,所以后面所有文章 Netty 版本都是基于 4.1.77,包括源码解析。

引入 Netty

xml 复制代码
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.77.Final</version>
</dependency>

服务端

服务端的逻辑非常简单,监听 8081 端口,当有客户端连接进来时打印:"**,已连接",同时打印客户端发送过来的消息。

java 复制代码
public class HelloWorldServer {
    public static void main(String[] args) {
        // 创建服务端启动引导器
        ServerBootstrap bootstrap = new ServerBootstrap();
        // 配置线程模型
        bootstrap.group(new NioEventLoopGroup());
        // 指定服务端的 IO 模型
        bootstrap.channel(NioServerSocketChannel.class);
        // 定义处理器 Handler
        bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                // 解码
                ch.pipeline().addLast(new StringDecoder());

                ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                        System.out.println(ctx.channel() + ",hello world");
                    }

                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        System.out.println(new Date() + ":" + msg);
                    }
                });
            }
        });
        // 绑定 8081 端口
        bootstrap.bind(8081);
    }
}

大明哥依次解释上面代码

  • ServerBootstrap bootstrap = new ServerBootstrap();:创建服务端启动引导器,ServerBootstrap 将用于引导服务端的启动工作。
  • bootstrap.group(new NioEventLoopGroup());:给引导器配置一个线程组,也就是 Netty 的线程模型,我们知道 Netty 是基于 Reactor 的线程模型,这里使用的单线程模型,即接受连接和业务处理都是使用同一个线程。
  • bootstrap.channel(NioServerSocketChannel.class);:指定服务端的 IO 模型,这里我们定义的是 NIO,当然你也可以使用 BIO(OioServerSocketChannel.class),但是一般都不推荐,因为 Netty 的优势就在于 NIO。
  • bootstrap.childHandler():这里是定义业务逻辑处理器,简单来说就是客户端向服务端做的操作(连接、读、写),服务端都是在这里进行处理的。
  • bootstrap.bind(8081):绑定 8081 端口

一个最简单的 Netty 服务单程序就写完了。

客户端

客户端就做一件事,连接服务端,然后不停地往服务端发送 "hello world"。

java 复制代码
public class HelloWorldClient {
    public static void main(String[] args) throws InterruptedException {
        // 客户端引导器
        Bootstrap bootstrap = new Bootstrap();
        // 配置线程组
        bootstrap.group(new NioEventLoopGroup());
        // 指定 IO 类型为 NIO
        bootstrap.channel(NioSocketChannel.class);
        // 配置 IO 处理器
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new StringEncoder());
            }
        });
        // 建立连接
        Channel channel = bootstrap.connect("127.0.0.1",8081).channel();
        // 发送消息
        while (true) {
            channel.writeAndFlush("hello world..");
            TimeUnit.SECONDS.sleep(5);
        }
    }
}

客户端与服务端的逻辑差不多,只不过它使用的是 Bootstrap,Bootstrap 为客户端启动的引导器,它负责启动客户端和连接服务端。创建引导器后,就和服务端一样了,需要配置线程模型,指定 IO 类型,配置 IO 处理器,最后与服务端建立连接。

  • bootstrap.connect("127.0.0.1",8081)connect() 方法与服务端进行连接,这里需要注意 connect() 方法是一个异步方法,它返回的是 ChannelFuture,调用 channel() 方法可以获取到对应的 channel(代码里面这种方式处理不是很优雅,我们后续再来讲)。
  • channel.writeAndFlush():向服务端发送消息。

到这里一个简单的 Netty 应用就完成了,是不是比使用 NIO 简便很多,也非常清晰。

运行结果

从服务端打印的日志可以看出,当客户端连接服务端后,打印日志:[id: 0xee05e542, L:/127.0.0.1:8081 - R:/127.0.0.1:53354],hello world,然后每隔 5 秒钟输出 hell world,这和我们开始的预期一样。

可能小伙伴们对上面的代码还不是很理解,对上面的 ServerBootstrap、group()channel() 等方法都不明白什么意思,没有关系,因为这篇文章仅仅只是让你对 Netty 有一个简单的认识,后面文章大明哥会将这些概念全部都讲的明明白白的。

【注】:上面两段代码有些瑕疵,但是为了小伙伴更加容易接受,大明哥就尽可能地简单演示,后面会基于它来优化
代码:m6z.cn/5zJPpt

相关推荐
Asthenia041219 分钟前
什么是语法分析 - 编译原理基础
后端
Asthenia041232 分钟前
理解词法分析与LEX:编译器的守门人
后端
uhakadotcom33 分钟前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
Asthenia04122 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9652 小时前
ovs patch port 对比 veth pair
后端
Asthenia04122 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom2 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide2 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9653 小时前
qemu 网络使用基础
后端
Asthenia04123 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端