Netty 体系结构

107 阅读5分钟

前言

用于创建高性能网络应用程序的高级框架

java网络编程的演化过程

异步通信和事件驱动处理

Netty的核心组件

核心网络协议

数据处理层

并发模型

关注点分离---业务和网络逻辑解耦 模块化和可用性 可测试性作为首要的要求

阻塞


ServerSocket serverSocket = new ServerSocket(portNumber);

Socket clientSocket = serverSocket.accept();

BufferedReader in = new BufferedReader(

new InputStreamReader(clientSocket.getInputStream()));

PrintWriter out =

new PrintWriter(clientSocket.getOutputStream(), true);

String request, response;

while ((request = in.readLine()) != null) {

if ("Done".equals(request)) {

break;

}

response = processRequest(request);

out.println(response);

}

实现了Socket API 的基本模式

accept方法将会一直阻塞到一个连接建立。只能同时处理一个连接,要管理多个并发客户端,需要为每一个新的客户端socket创建一个新的Thread

image.png

本地套接字很早就提供了非阻塞调用:

使用setsocketopt方法配置套接字,以便读/写调用在没有数据的时候立即返回。

可以使用操作系统的事件通知API注册一组非阻塞套接字,以确定它们中是否有任何的套接字已经有数据可供读写

非阻塞

image.png

java.nio.channels.Selector 是java的非阻塞I/O实现的关键。它使用事件通知API以确定在一组非阻塞套接字中那些已经就绪能够进行IO相关操作。

因为可以在任何的时间检查任意的读写操作的完成状态,所以一个单一的线程便可以处理多个并发的连接。

netty

  • 设计:统一的API,支持多种传输类型,阻塞和非阻塞的;简单而强大的线程模型;真正的无连接数据报套接字支持;链接逻辑组件以支持复用
  • 易于使用:详细javadoc和大量示例;不需要超过jdk6
  • 性能:拥有比java核心api更高的吞吐量以及更低延迟;得益于池化和复用,拥有更低的资源消耗;最少的内存复制
  • 健壮性:不会因为慢速、快速或者超载的连接而导致内存溢出;消除在高速网络中NIO应用程序常见的不公平读写比率
  • 安全性:完全的SSL/TLS以及StartTLS支持;可用于受限环境下,如Applet和OSGI
  • 社区驱动:发布快速而且频繁

核心组件

  • Channel
  • 回调
  • Future
  • 事件和ChannelHandler

Channel 是Java 非阻塞的IO的一个基本构造:它代表一个到实体(一个硬件设备、文件、网络套接字、一个能够执行一个或多个不同IO操作的程序组件)的开放连接,如读、写操作

可以将之看作传入、传出数据的载体,因此它可以被打开或被关闭,连接或者断开连接

回调:就是一个方法,一个指向已经被提供给另一个方法的引用。这使得后者可以在适当的时候调用前者。(回调在广泛的编程场景中都有应用,而且也是在操作完成后通知相关方最常见的方式之一)

Netty在内部使用了回调来处理事件,当一个回调被触发时,相关事件可以被一个interfaceChannelHandler的实现处理。

public class ConnectHandler extends ChannelInboundHandlerAdapter {

@Override

public void channelActive(ChannelHandlerContext ctx)

throws Exception {

System.out.println(

   "Client " + ctx.channel().remoteAddress() + " connected");

}

}

Futute:提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将来在未来的某个时刻完成,并提供对其结果的访问。

这里未使用JDK提供的Future实现。而是netty自己实现的ChannelFuture。

Channel channel = ...;

// Does not block

ChannelFuture future = channel.connect(

new InetSocketAddress("192.168.0.1", 25));

future.addListener(new ChannelFutureListener() {

@Override

public void operationComplete(ChannelFuture future) {

if (future.isSuccess()){

ByteBuf buffer = Unpooled.copiedBuffer(

"Hello",Charset.defaultCharset());

ChannelFuture wf = future.channel()

.writeAndFlush(buffer);

....

} else {

Throwable cause = future.cause();

cause.printStackTrace();

}

}

});

事件和ChannelHandler 使用不同的事件来通知我们状态的改变或操作的状态:

记录日志

数据转换

流控制

应用程序逻辑

netty 是一个网络编程框架,事件按照它们入站或出站的数据流相关性进行分类: 入站:

  • 连接已被激活或者连接失活
  • 数据读取
  • 用户事件
  • 错误事件 出站:
  • 打开或者关闭到远程节点的连接
  • 将数据写到或者冲刷到套接字

每个事件都可以被分发到ChannelHandler类中的某个用户实现的方法。这是一个很好的将事件驱动范式直接转换未应用程序构件块的例子。

总结

异步编程模型是建立在Future和回调的概念上的,而将事件派发到ChannelHandler的方法则发生在更深的层次上。结合在一起,将这些元素就提供了一个处理环境,是你的应用程序逻辑可以独立于任何网络操作相关的顾虑而独立演化。

拦截操作以及高速地转换入站数据和出站数据,都只需要你提供回调或者利用操作所返回的Future,这使得链接操作变得简单又高效。

通过事件触发将Selector从应用程序中抽象出来,消除了所有本来将需要手动编写的派发代码。在内部,将会未每个Channel分配一个EventLoop,用来处理所有事件:

注册感兴趣的事件

将事件派发给ChannelHandler

安排进一步的动作

EventLoop本身是一个线程驱动,处理了一个Channel的所有IO事件,并且在该EventLoop的整个生命周期内不会改变。