Netty 学习笔记(一)、Netty 背景介绍

概述

在日常工作过程中,我经常听到同事提起 Netty 这个概念,只知道它是一款易于使用的高性能客户端/服务器框架。再加上公司 SDK 本身对 Netty 做了层层封装,以至于到现在只会使用,不知原理。为了填补这块的知识盲区,我打算就 Netty 学习路线单独开一专题,一来记录学习过程,二来通过博客加深理解,实现学习、理解两开花。


背景

早期的客户端/服务器基于长连接 Socket 实现,Java 很早就提供了丰富的 Socket API。即使如此,想要实现复杂的客户端/服务器仍需要大量的样板代码。并且通过这些 API 实现的长连接本身就是 阻塞 的,这对于系统的性能有极大的影响。

阻塞:结果返回前,当前线程将会被挂起,调用线程只有在获取到执行结果后才会恢复。

下面我通过简单的代码实现早期 Socket 客户端/服务器 示例:

Socket 服务端:

public class SocketServer {

    public static void main(String[] args) throws InterruptedException {
        try {
            // 创建Socket服务端,监听8888端口等待客户端连接
            ServerSocket serverSocket = new ServerSocket(8888);
            // 阻塞等待客户端连接,连接成功后返回Socket对象,通过该对象实现客户端和服务器之间的交互
            Socket socket = serverSocket.accept();
            // 创建字符缓存输入流
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 阻塞读取客户端发来的数据
            String request = reader.readLine();
            System.out.println("收到客户端发送的数据:" + request);
            // 创建字符缓存输出流
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            // 向服务器返回字符串,'Hello Netty'
            writer.write("Hello Netty\n");
            // 清空缓冲区,确保所有数据发送完成
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Socket 客户端:

public class SocketClient {

    public static void main(String[] args) throws IOException {
        Socket socket = null;
        try {
            // 创建Socket客户端,连接127.0.0.1的9999端口
            socket = new Socket("127.0.0.1", 8888);
            // 创建字符缓冲输出流
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            // 向服务端发送字符串 'Hello World'
            writer.write("Hello World\n");
            // 清空缓冲区,确保所有数据发送完成
            writer.flush();
            // 创建字符缓冲输入流
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 阻塞读取服务器返回的数据
            String response = reader.readLine();
            System.out.println("收到服务器的返回信息:" + response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行结果

收到客户端发送的数据:Hello World
收到服务器的返回信息:Hello Netty

上述代码绝大部分内容已经通过注解的形式解释,这里我主要说明两点:

  • xxx.flush():通过流写数据时,数据不是直接写到目标媒介,而是首先会写入缓冲区中。如果在数据写入缓冲区就关闭流的话,会丢失一部分数据。此时调用 flush() 方法可以清空缓冲区中的数据,确保数据完全传输。

  • 除了通过 BufferedWriter 类的 write() 写数据外,还可以通过 PrintStream 类的 println() 方法写数据。需要特别注意的一点是:使用 write() 方法写数据时必须通过换行符结束。

上述示例代码中客户端/服务端模型缺点很明显:服务端只能为某一客户端服务。为了使服务端能够同时为多个客户端提供服务,可以在客户端连接后创建新的线程处理该客户端请求,简单来说就是主线程一直循环等待客户端连接,每个客户端连接成功后创建新的线程处理它的请求,因为它效率低下的原因,这里我就不详细展开。

虽然我们可以通过编码的方式让服务端同时服务多个客户端,但长连接本身阻塞的消耗还是不能解决。任何时刻可能存在多个线程处于阻塞状态,等待流读取数据或等待客户端连接。这种处理方式给系统带来了很大的资源消耗,这也是 Socket 被市场淘汰的主要原因。


NIO

为了解决原生 Socket 效率低下的问题,在 JDK1.4 时,Java 引入了非阻塞 io 包:java.nio

NIO 是 Nonblocking I/O 的缩写,即非阻塞 io。和普通 I/O 相比,它引入以 SELECTOR(选择器) 为核心的新 I/O 模型,下面我们通过结构图具体对比两种模型:

传统 Socket I/O 模型:
原生 I/O 模型
如上图所示,传统 I/O 对每个客户端建立 Socket 连接,并且每个 Socket 连接对应单独线程完成交互。当客户端数量较大是,服务端需要维护大量的服务线程,此时 上下文切换 所带来的消耗将不能忽略,并且线程本身需要耗费一定的资源,总的来说这种方式是很耗费资源的。

新 Socket NIO 模型:
新 NIO 模型
与传统 I/O 相比,NIO 引入了 Selector 选择器,通过它来决定此时对哪一组 Socket 执行 I/O 操作。相比传统 I/O 模型,NIO 模型具有以下优点:

  • 可以使用较少的线程处理更多的 Socket 连接,这意味着上下文切换开销更少
  • 等待 I/O 读取期间,线程可以去干其它事情,也就是说线程是非阻塞的

我们可以直接使用 NIO 包实现非阻塞线程模型,但 NIO 包本身是比较复杂的,如果想要使用更高效的非阻塞线程模型,建议使用 Netty,Netty 框架本身就是对 NIO 的一种封装,至此关于 Netty 背景的介绍全部完成。


参考:
《Netty 实战》
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值