netty框架学习记录
1. 简介
官方定义为:”Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器
和客户端”。
纵观Java系的多种服务器/大数据框架,都离不开Netty做出的贡献,本文对Netty做一个简单的概述
2. 主要特性
Netty有很多重要的特性,主要特性如下:
- 优雅的设计
- 统一的API接口,支持多种传输类型,例如OIO,NIO
- 简单而强大的线程模型
- 丰富的文档
- 卓越的性能
- 拥有比原生Java API 更高的性能与更低的延迟
- 基于池化和复用技术,使资源消耗更低
- 安全性
- 完整的SSL/TLS以及StartTLS支持
- 可用于受限环境,如Applet以及OSGI
3. 主要术语
在正式开始之前,先对Netty涉及到的一些术语做个简单的说明
3.1 IO模型:BIO/NIO/Netty
3.1.1 BIO(Blocking IO):阻塞IO
Socket 创建一个新的 Thread,线程模型如下图所示:
BIO 有的称之为 basic(基本) IO,有的称之为 block(阻塞) IO,主要应用于文件 IO 和网络 IO, 这里不再说文件 IO, 在 JDK1.4 之前,我们建立网络连接的时候只能采用 BIO,需要先在服务端启动一个 ServerSocket,然后在客户端启动 Socket 来对服务端进行通信,默认情况下服务端需要对每个请求建立一个线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝,如果有的话,客户端线程会等待请求结束后才继续执行, 这就是阻塞式 IO。
//BIO 服务器端程序 public class TCPServer { public static void main(String[] args) throws Exception { //1.创建 ServerSocket 对象 ServerSocket ss = new ServerSocket(9999); while (true) { //2.监听客户端 Socket s = ss.accept(); //阻塞 // 3.从连接中取出输入流来接收消息 InputStream is = s.getInputStream(); //阻塞 byte[] b = new byte[10]; is.read(b); String clientIP = s.getInetAddress().getHostAddress(); System.out.println(clientIP + "说:" + new String(b).trim()); //4.从连接中取出输出流并回话 OutputStream os = s.getOutputStream(); os.write("hello".getBytes()); //5.关闭 s.close(); } } }
上述代码编写了一个服务器端程序,绑定端口号 9999,accept 方法用来监听客户端连接, 如果没有客户端连接,就一直等待,程序会阻塞到这里。
//BIO 客户端程序 public class TCPClient { public static void main(String[] args) throws Exception { while (true) { //1.创建 Socket 对象 Socket s = new Socket("127.0.0.1", 9999); //2.从连接中取出输出流并发消息 OutputStream os = s.getOutputStream(); System.out.println("请输入:"); Scanner sc = new Scanner(System.in); String msg = sc.nextLine(); os.write(msg.getBytes()); //3.从连接中取出输入流并接收回话 InputStream is = s.getInputStream(); //阻塞 byte[] b = new byte[20]; is.read(b); System.out.println("说:" + new String(b).trim()); //4.关闭 s.close(); } } }
上述代码编写了一个客户端程序,通过 9999 端口连接服务器端,getInputStream 方法用来 等待服务器端返回数据,如果没有返回,就一直等待,程序会阻塞到这里。
这个仅仅只是简单的演示代码,如果真正的使用怎么做呢?加线程呗,一个用户过来就新建一个线程,然后每个线程里面一个while循环阻塞在里面,这是很恐怖的一件事,这些就会带来下面三个问题:
1、资源受限,大量的线程阻塞在那里,对于服务器来说是很浪费资源的一件事。
2、线程切换频繁,我们知道java线程如果优先级相同是抢占式的也就是随机的,线程数量过多,对于单核cpu切换来说是很影响性能的。
3、上面例子可以看到Bio是以byte为单位的。
3.1.2 NIO(Non Blocking IO):非阻塞IO
Java的NIO特性在JDK 1.4中引入,其结构如下:
Jdk1.4之后提出了Nio,NIO 和 BIO 有着相同的目的和作用,但是它们的实现方式完全不同,BIO 以流的方式处理数据,**而 NIO 以块的方式处理数据,**块 I/O 的效率比流 I/O 高很多。另外,NIO 是非阻塞式的, 这一点跟 BIO 也很不相同,使用它可以提供非阻塞式的高伸缩性网络。 NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统的 BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通 道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
虽然Java 的NIO在性能上比BIO已经相当的优秀,但是要做到如此正确和安全并
不容易。特别是,在高负载下可靠和高效地处理和调度 I/O 操作是一项繁琐而且容易出错的任务,此时就时Netty上场的时间了。
3.1.3 Netty
Netty对NIO的API进行了封装,通过以下手段让性能又得到了一定程度的提升
1. 使用多路复用技术,提高处理连接的并发性
2. 零拷贝:
1. Netty的接收和发送数据采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝
2. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象进行一次操作
3. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题
3. 内存池:为了减少堆外直接内存的分配和回收产生的资源损耗问题,Netty提供了基于内存池的缓冲区重用机制
4. 使用主从Reactor多线程模型,提高并发性
5. 采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降
6. 默认使用Protobuf的序列化框架
7. 灵活的TCP参数配置
Socket
简介
Spring Boot使用Netty实现客户端与服务器通信
https://blog.csdn.net/qmqm011/article/details/100156010/什么是Decoder和Encoder
在Netty里面,有四个核心概念,它们分别是:
- Channel:一个客户端与服务器通信的通道。
- ChannelHandler:业务逻辑处理器, 通常情况下,业务逻辑都是存在于ChannelHandler之中。
- ChannelInboundHandler:输入处理器
- ChannelOutboundHandler:输出处理器
- ChannelPipeline:用于存放ChannelHandler的双向链表。
- ChannelContext:通信管道的上下文
它们之间的交互流程是:
- 事件传递给 ChannelPipeline 的第一个 ChannelHandler
- ChannelHandler 通过关联的 ChannelHandlerContext 传递事件给 ChannelPipeline 中的 下一个
而我们要讲的Decoder和Encoder,就是ChannelInboundHandler和ChannelOutboundHandler,分别用于在数据流进来的时候将字节码转换为消息对象和数据流出去的时候将消息对象转换为字节码。
socket的标准参数,并不是netty自己的
具体为:
-
ChannelOption.SO_BACKLOG, 1024
BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
-
ChannelOption.SO_KEEPALIVE, true
是否启用心跳保活机制。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。
-
ChannelOption.TCP_NODELAY, true
在TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。这里就涉及到一个名为Nagle的算法,该算法的目的就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
TCP_NODELAY就是用于启用或关于Nagle算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为true关闭Nagle算法;如果要减少发送次数减少网络交互,就设置为false等累积一定大小后再发送。默认为false。
- ChannelOption.SO_REUSEADDR, true
SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。 SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。 SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。 SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)
- ChannelOption.SO_RCVBUF AND ChannelOption.SO_SNDBUF
- ChannelOption.ALLOCATOR
Netty4使用对象池,重用缓冲区
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);