6

怎么通俗的理解Netty呢?

 2 years ago
source link: https://blog.51cto.com/u_15489434/5080320
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Netty(3.X)

有了Netty,你可以实现自己的HTTP服务器,FTP服务器,UDP服务器,RPC服务器,WebSocket服务器,Redis的Proxy服务器,MySQL的Proxy服务器等等。

如果你想知道Nginx是怎么写出来的,如果你想知道Tomcat和Jetty是如何实现的,如果你也想实现一个简单的Redis服务器,那都应该好好理解一下Netty,它们高性能的原理都是类似的。

看一下传统的HTTP服务器的原理:

  1. 创建一个ServerSocket,监听并绑定一个端口
  2. 一系列客户端来请求这个端口
  3. 服务器使用Accept,获得一个来自客户端的Socket连接对象
  4. 启动一个新线程处理连接
  1. 读Socket,得到字节流
  2. 解码协议,得到Http请求对象
  3. 处理Http请求,得到一个结果,封装成一个HttpResponse对象
  4. 编码协议,将结果序列化字节流
  5. 写Socket,将字节流发给客户端
  1. 继续循环步骤3

HTTP服务器之所以称为 HTTP 服务器,是因为编码解码协议是HTTP协议,如果协议是Redis协议,那它就成了Redis服务器,如果协议是WebSocket,那它就成了WebSocket服务器,等等。

使用Netty你就可以定制编解码协议,实现自己的特定协议的服务器。

上面我们说的是一个传统的多线程服务器,这个也是Apache处理请求的模式。在高并发环境下,线程数量可能会创建太多,操作系统的任务调度压力大,系统负载也会比较高。那怎么办呢?

于是 NIO 诞生了,NIO并不是Java独有的概念,NIO代表的一个词汇叫着IO多路复用。它是由操作系统提供的系统调用,早期这个操作系统调用的名字是 select ,但是性能低下,后来渐渐演化成了Linux下的epoll和Mac里的kqueue。我们一般就说是epoll,因为没有人拿苹果电脑作为服务器使用对外提供服务。而Netty就是基于Java NIO技术封装的一套框架。为什么要封装,因为原生的Java NIO使用起来没那么方便,而且还有臭名昭著的bug,Netty把它封装之后,提供了一个易于操作的使用模式和接口,用户使用起来也就便捷多了。

那NIO究竟是什么东西呢?

NIO的全称是NoneBlocking IO,非阻塞IO,区别与BIO,BIO的全称是Blocking IO,阻塞IO。那这个阻塞是什么意思呢?

  1. Accept是阻塞的,只有新连接来了,Accept才会返回,主线程才能继
  2. Read是阻塞的,只有请求消息来了,Read才能返回,子线程才能继续处理
  3. Write是阻塞的,只有客户端把消息收了,Write才能返回,子线程才能继续读取下一个请求

所以传统的多线程服务器是BlockingIO模式的,从头到尾所有的线程都是阻塞的。这些线程就干等在哪里,占用了操作系统的调度资源,什么事也不干,是浪费。

那么NIO是怎么做到非阻塞的呢?

它用的是 事件机制 。它可以用一个线程把Accept,读写操作,请求处理的逻辑全干了。如果什么事都没得做,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。

while true {
events = takeEvents(fds) // 获取事件,如果没有事件,线程就休眠
for event in events {
if event.isAcceptable {
doAccept() // 新链接来了
} elif event.isReadable {
request = doRead() // 读消息
if request.isComplete() {
doProcess()
}
} elif event.isWriteable {
doWrite() // 写消息
}
}
}

Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象。

在Netty里面,Accept连接可以使用单独的线程池去处理,读写操作又是另外的线程池来处理。

Accept连接和读写操作也可以使用同一个线程池来进行处理。而请求处理逻辑既可以使用单独的线程池进行处理,也可以跟放在读写线程一块处理。线程池中的每一个线程都是NIO线程。用户可以根据实际情况进行组装,构造出满足系统需求的并发模型。

Netty提供了内置的常用编解码器,包括行编解码器[一行一个请求],前缀长度编解码器[前N个字节定义请求的字节长度],可重放解码器[记录半包消息的状态],HTTP编解码器,WebSocket消息编解码器等等

Netty提供了一些列生命周期回调接口,当一个完整的请求到达时,当一个连接关闭时,当一个连接建立时,用户都会收到回调事件,然后进行逻辑处理。

Netty可以同时管理多个端口,可以使用NIO客户端模型,这些对于RPC服务是很有必要的。

Netty除了可以处理TCP Socket之外,还可以处理UDP Socket。

在消息读写过程中,需要大量使用ByteBuffer,Netty对ByteBuffer在性能和使用的便捷性上都进行了优化和抽象。

1)JBoss做的一个Jar包。

2)目的:快速开发高性能、高可靠性的网络服务器和客户端程序。

3)优点:提供异步的、事件驱动的网络应用程序框架和工具。

public void run() {
// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));

// Set up the pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new EchoServerHandler());
}
});

// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(port));
}

这里 EchoServerHandler 是其业务逻辑的实现者,大致代码如下:

public class EchoServerHandler extends SimpleChannelUpstreamHandler {

@Override
public void messageReceived(
ChannelHandlerContext ctx, MessageEvent e) {
// Send back the received message to the remote peer.
e.getChannel().write(e.getMessage());
}

Netty的事件驱动机制

看看 EchoServerHandler 的代码,其中的参数: public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) ,MessageEvent就是一个事件。这个事件携带了一些信息,例如这里 e.getMessage()就是消息的内容,而 EchoServerHandler 则描述了处理这种事件的方式。一旦某个事件触发,相应的Handler则会被调用,并进行处理。这种事件机制在UI编程里广泛应用,而Netty则将其应用到了网络编程领域。

在Netty里,所有事件都来自 ChannelEvent 接口,这些事件涵盖监听端口、建立连接、读写数据等网络通讯的各个阶段。而事件的处理者就是 ChannelHandler ,这样,不但是业务逻辑,连网络通讯流程中底层的处理,都可以通过实现 ChannelHandler 来完成了。事实上,Netty内部的连接处理、协议编解码、超时等机制,都是通过handler完成的。

下图描述了Netty进行事件处理的流程。 Channel 是连接的通道,是ChannelEvent的产生者,而 ChannelPipeline 可以理解为ChannelHandler的集合。

怎么通俗的理解Netty呢?_服务器

Netty的源码阅读

org
└── jboss
└── netty
├── bootstrap 配置并启动服务的类
├── buffer 缓冲相关类,对NIO Buffer做了一些封装
├── channel 核心部分,处理连接
├── container 连接其他容器的代码
├── example 使用示例
├── handler 基于handler的扩展部分,实现协议编解码等附加功能
├── logging 日志
└── util 工具类</pre>

怎么通俗的理解Netty呢?_服务器_02

除了之前说到的事件驱动机制之外,Netty的核心功能还包括两部分:

  • Zero-Copy-Capable Rich Byte Buffer零拷贝的Buffer。为什么叫零拷贝?因为在数据传输时,最终处理的数据会需要对单个传输层的报文,进行组合或者拆分。NIO原生的ByteBuffer无法做到这件事,而Netty通过提供Composite(组合)和Slice(切分)两种Buffer来实现零拷贝。这部分代码在 org.jboss.netty.buffer 包中。这里需要额外注意,不要和操作系统级别的Zero-Copy混淆了, 操作系统中的零拷贝主要是用户空间和内核空间之间的数据拷贝, NIO中通过DirectBuffer做了实现.
  • Universal Communication API统一的通讯API。这个是针对Java的Old I/O和New I/O,使用了不同的API而言。Netty则提供了统一的API( org.jboss.netty.channel.Channel )来封装这两种I/O模型。这部分代码在 org.jboss.netty.channel 包中。

​此外,Protocol Support功能通过handler机制实现。​


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK