Netty 框架学习 —— 初识 Netty


Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端java


Java 网络编程

早期的 Java API 只支持由本地系统套接字库提供的所谓的阻塞函数,下面的代码展现了一个使用传统 Java API 的服务器代码的普通示例编程

// 建立一个 ServerSocket 用以监听指定端口上的链接请求
ServerSocket serverSocket = new ServerSocket(5000);
// 对 accept 方法的调用将被阻塞,直到一个链接创建
Socket clientSocket = serverSocket.accept();
// 这些流对象都派生于该套接字的流对象
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;
// 客户端发送了 "Done" 则退出循环
while ((request = in.readLine()) != null) {
    if ("Done".equals(request)) {
        break;
    }
    // 请求被传递给服务器的处理方法
    response = proce***equest(request);
    // 服务器的响应被发送给客户端
    out.println(response);
}

这段代码只能同时处理一个链接,要管理多个客户端,就要为每一个新的客户端 Socket 建立一个新的 Thread,让咱们来考虑一下这种方案的影响:安全

  • 在任什么时候候都会有大量线程处于休眠状态,形成资源浪费
  • 须要为每一个线程的调用栈都分配内存
  • 线程的上下文切换会带来开销

这种并发方案对于中小数量的客户端还算理想,但不能很好地支持更大的并发,幸运的是,还有另外一种解决方案服务器


Java NIO

NIO(Non-blocking I/O,也称 New I/O),是一种同步非阻塞的 I/O 模型,也是 I/O 多路复用的基础。传统的 IO 流是阻塞的,这意味着,当一个线程调用读或写操做时,线程将被阻塞,直至数据被彻底读取或写入。NIO 的非阻塞模式,使一个线程进行读或写操做时,若是目前无数据可用时,就不作操做,而不是保持线程阻塞,因此直至数据就绪之前,线程能够继续作其余事情网络

class java.nio.channels.Selector 是 Java 非阻塞 IO 实现的关键。它使用事件通知 API 以肯定在一组非阻塞套接字中有哪些已经就绪并能进行 IO 相关操做。由于能够在任什么时候间点任意检查读操做或写操做的完成状况,因此单一线程能够处理多个并发的链接并发

与阻塞 IO 模型相比,这种模型提供了更好的资源管理:框架

  • 使用较少的线程即可以处理许多链接,减小内存管理和上下文切换所带来的开销
  • 当没有 IO 操做须要处理时,线程也能够用户其余任务

尽管 Java NIO 如此高效,但要作到正确和安全并不容易,在高负载下可靠和高效地处理和调度 IO 操做是一项烦琐且容易出错的任务,所幸,有 Netty 能帮助咱们解决问题异步


Netty

Netty 是一个普遍使用的 Java 网络编程框架,它隐藏了 Java 高级 API 背后的复杂性,提供一个易于使用的 API 的客户端/服务器框架。在这里咱们将要讨论 Netty 的主要构件:ide

1. Channel

Channel 是 Java NIO 的一个基本构造,能够把 Channel 简单看做是传入(入站)或传出(出站)数据的载体。所以,它能够被打开或关闭,链接或断开链接异步编程

2. 回调

一个回调其实就是一个方法,Netty 使用回调来处理事件,当一个回调被触发时,相关的事件能够被 ChannelHandler 的实现处理。下面的代码展现了这样一个例子:当一个新的链接已经创建,ChannelHandler 的 channelActive() 的回调方法将会被调用,并打印出一条信息

public class ConnectHandler extends ChannelInboundHandlerAdapter {
    
    @override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client " + ctx.channel().remoteAddress() + " connected");
    }
}

3. Future

Future 提供了另外一种在操做完成时通知应用程序的方式,它将在将来的某个时刻完成,并提供对其结果的访问。虽然 Java 提供了 Future 的一种实现,但须要手动检查对应操做是否已经完成,或一直阻塞直到它完成,十分烦琐。Netty 提供了本身的实现 ChannelFuture,用于执行异步操做的时候使用

ChannelFuture 提供了几种额外的方法,这些方法使得咱们可以注册一个或多个 ChannelFutureListener 实例。监听器的回调方法 operationComplete() 将会在对应操做完成时被调用。每一个 Netty 的 IO 操做都会返回一个 ChannelFuture,它们都不会被阻塞,能够同时作其余工做,更加有效的利用资源

Channel channel = ...;
// 异步地链接到远程结点
ChannelFuture future = channel.connect(new InetSocketAddress("192.168.0.1", 25));
// 注册一个 ChannelFutureListener
future.addListener(new ChannelFutureListener() {
    
    @Override
    public void operationComplete(ChannelFuture future) {
        // 若是操做成功
        if(future.isSuccess()) {
            ...
        } else {
            // 发生异常
            ...
        }
    }
});

4. 事件和 ChannelHandler

Netty 使用不一样的事件来触发合适的动做,事件是按照与入站或出站数据流的相关性进行分类的,可能由入站数据或相关状态更改而触发的事件包括:

  • 链接已被激活或失效
  • 数据读取
  • 用户事件
  • 错误事件

出站事件是将来将会触发的某个动做的操做结果,包括:

  • 打开或关闭到远程结点的链接
  • 将数据写到或冲刷到套接字

每一个事件均可分发给 ChannelHandler 类中的某个用户实现的方法,下图展现了一个事件如何被一个 ChannelHandler 链处理

Netty 提供了大量预约义的 ChannelHandler 实现,供开发者使用

5. 总结

Netty 的异步编程模型是创建在 Future 和回调的概念之上的,将事件派发到 ChannelHandler 拦截并高速地转换入站数据和出站数据,开发者只须要提供回调或者利用返回的 Future 便可。Netty 经过触发事件将 Selector 从应用程序中抽象出来,消除了本需手写的派发代码。在内部,将会为每一个 Channel 分配一个 EventLoop,用于处理全部事件,包括:

  • 注册感兴趣的时间
  • 将事件派发给 ChannelHandler
  • 安排进一步的动做

EventLoop 自己只有一个线程驱动,处理了一个 Channel 的全部 IO 事件,这个简单而强大的设计消除了可能在 ChannelHandler 实现中须要进行同步的任何顾虑,所以咱们只需专一于提供正确的逻辑便可

相关文章
相关标签/搜索