高清思惟导图原件(xmind/pdf/jpg
)能够关注公众号:一枝花算不算浪漫
回复nio
便可。前端
前言
抱歉很久没更原创文章了,看了下上篇更新时间,已经拖更一个多月了。java
这段时间也一直在学习Netty
相关知识,由于涉及知识点比较多,也走了很多弯路。目前网上关于Netty学习资料玲琅满目,不知如何下手,其实你们都是同样的,学习方法和技巧都是总结出来的,咱们在没有找到很好的方法以前不如循序渐进先从基础开始,通常从总分总的渐进方式,既观森林,又见草木。linux
以前恰巧跟杭州一个朋友小飞也提到过,二者在这方面的初衷是一致的,也但愿更多的朋友可以加入一块儿学习和探讨。(PS:本篇文章是和小飞一块儿学习整理所得~)程序员
Netty
是一款提供异步的、事件驱动的网络应用程序框架和工具,是基于NIO
客户端、服务器端的编程框架。因此这里咱们先以NIO
和依赖相关的基础铺垫来进行剖析讲解,从而做为Netty
学习之旅的一个开端。算法
1、网络编程基础回顾
1. Socket
Socket
自己有“插座”的意思,不是Java中特有的概念,而是一个语言无关的标准,任何能够实现网络编程的编程语言都有Socket
。在Linux
环境下,用于表示进程间网络通讯的特殊文件类型,其本质为内核借助缓冲区造成的伪文件。既然是文件,那么理所固然的,咱们可使用文件描述符引用套接字。编程
与管道相似的,Linux
系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操做一致。区别是管道主要应用于本地进程间通讯,而套接字多应用于网络进程间数据的传递。设计模式
能够这么理解:Socket
就是网络上的两个应用程序经过一个双向通讯链接实现数据交换的编程接口API。数组
Socket
通讯的基本流程具体步骤以下所示:缓存
(1)服务端经过Listen
开启监听,等待客户端接入。服务器
(2)客户端的套接字经过Connect
链接服务器端的套接字,服务端经过Accept
接收客户端链接。在connect-accept
过程当中,操做系统将会进行三次握手。
(3)客户端和服务端经过write
和read
发送和接收数据,操做系统将会完成TCP
数据的确认、重发等步骤。
(4)经过close
关闭链接,操做系统会进行四次挥手。
针对Java编程语言,java.net
包是网络编程的基础类库。其中ServerSocket
和Socket
是网络编程的基础类型。
SeverSocket
是服务端应用类型。Socket
是创建链接的类型。当链接创建成功后,服务器和客户端都会有一个Socket
对象示例,能够经过这个Socket
对象示例,完成会话的全部操做。对于一个完整的网络链接来讲,Socket
是平等的,没有服务器客户端分级状况。
2. IO模型介绍
对于一次IO操做,数据会先拷贝到内核空间中,而后再从内核空间拷贝到用户空间中,因此一次read
操做,会经历两个阶段:
(1)等待数据准备
(2)数据从内核空间拷贝到用户空间
基于以上两个阶段就产生了五种不一样的IO模式。
- 阻塞IO:从进程发起IO操做,一直等待上述两个阶段完成,此时两阶段一块儿阻塞。
- 非阻塞IO:进程一直询问IO准备好了没有,准备好了再发起读取操做,这时才把数据从内核空间拷贝到用户空间。第一阶段不阻塞但要轮询,第二阶段阻塞。
- 多路复用IO:多个链接使用同一个select去询问IO准备好了没有,若是有准备好了的,就返回有数据准备好了,而后对应的链接再发起读取操做,把数据从内核空间拷贝到用户空间。两阶段分开阻塞。
- 信号驱动IO:进程发起读取操做会当即返回,当数据准备好了会以通知的形式告诉进程,进程再发起读取操做,把数据从内核空间拷贝到用户空间。第一阶段不阻塞,第二阶段阻塞。
- 异步IO:进程发起读取操做会当即返回,等到数据准备好且已经拷贝到用户空间了再通知进程拿数据。两个阶段都不阻塞。
这五种IO模式不难发现存在这两对关系:同步和异步、阻塞和非阻塞。那么稍微解释一下:
同步和异步
- 同步: 同步就是发起一个调用后,被调用者未处理完请求以前,调用不返回。
- 异步: 异步就是发起一个调用后,马上获得被调用者的回应表示已接收到请求,可是被调用者并无返回结果,此时咱们能够处理其余的请求,被调用者一般依靠事件,回调等机制来通知调用者其返回结果。
同步和异步的区别最大在于异步的话调用者不须要等待处理结果,被调用者会经过回调等机制来通知调用者其返回结果。
阻塞和非阻塞
- 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,没法从事其余任务,只有当条件就绪才能继续。
- 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,能够先去干其余事情。
阻塞和非阻塞是针对进程在访问数据的时候,根据IO操做的就绪状态来采起的不一样方式,说白了是一种读取或者写入操做方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会当即返回一个状态值。
若是组合后的同步阻塞(blocking-IO
)简称BIO
、同步非阻塞(non-blocking-IO
)简称NIO
和异步非阻塞(asynchronous-non-blocking-IO
)简称AIO
又表明什么意思呢?
- BIO (同步阻塞I/O模式): 数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工做模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。可是实际上线程在等待水壶烧开的时间段什么都没有作。
- NIO(同步非阻塞): 同时支持阻塞与非阻塞模式,但这里咱们以其同步非阻塞I/O模式来讲明,那么什么叫作同步非阻塞?若是还拿烧开水来讲,NIO的作法是叫一个线程不断的轮询每一个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操做。
- AIO(异步非阻塞I/O模型): 异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询全部IO操做的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每一个水壶上面装了一个开关,水烧开以后,水壶会自动通知我水烧开了。
java
中的 BIO
、NIO
和AIO
理解为是 Java 语言
在操做系统层面对这三种 IO
模型的封装。程序员在使用这些 封装API 的时候,不须要关心操做系统层面的知识,也不须要根据不一样操做系统编写不一样的代码,只须要使用Java
的API就能够了。由此,为了使读者对这三种模型有个比较具体和递推式的了解,而且和本文主题NIO
有个清晰的对比,下面继续延伸。
Java BIO
BIO
编程方式一般是是Java的上古产品,自JDK 1.0-JDK1.4就有的东西。编程实现过程为:首先在服务端启动一个ServerSocket
来监听网络请求,客户端启动Socket
发起网络请求,默认状况下SeverSocket
会创建一个线程来处理此请求,若是服务端没有线程可用,客户端则会阻塞等待或遭到拒绝。服务器实现模式为一个链接一个线程,即客户端有链接请求时服务器端就须要启动一个线程进行处理。大体结构以下:
若是要让 BIO
通讯模型可以同时处理多个客户端请求,就必须使用多线程(主要缘由是 socket.accept()
、socket.read()
、 socket.write()
涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端链接请求以后为每一个客户端建立一个新的线程进行链路处理,处理完成以后,经过输出流返回应答给客户端,线程销毁。这就是典型的 一请求一应答通讯模型 。咱们能够设想一下若是这个链接不作任何事情的话就会形成没必要要的线程开销,不过能够经过线程池机制改善,线程池还可让线程的建立和回收成本相对较低。使用线程池机制改善后的 BIO
模型图以下:
BIO
方式适用于链接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,是JDK1.4之前的惟一选择,但程序直观简单易懂。Java BIO
编程示例网上不少,这里就不进行coding举例了,毕竟后面NIO
才是重点。
Java NIO
NIO
(New IO或者No-Blocking IO),从JDK1.4 开始引入的非阻塞IO
,是一种非阻塞
+ 同步
的通讯模式。这里的No Blocking IO
用于区分上面的BIO
。
NIO
自己想解决 BIO
的并发问题,经过Reactor模式
的事件驱动机制来达到Non Blocking
的。当 socket
有流可读或可写入 socket
时,操做系统会相应的通知应用程序进行处理,应用再将流读取到缓冲区或写入操做系统。
也就是说,这个时候,已经不是一个链接就 要对应一个处理线程了,而是有效的请求,对应一个线程,当链接没有数据时,是没有工做线程来处理的。
当一个链接建立后,不须要对应一个线程,这个链接会被注册到 多路复用器
上面,因此全部的链接只须要一个线程就能够搞定,当这个线程中的多路复用器
进行轮询的时候,发现链接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
NIO
提供了与传统BIO模型中的Socket
和ServerSocket
相对应的SocketChannel
和ServerSocketChannel
两种不一样的套接字通道实现,以下图结构所示。这里涉及的Reactor
设计模式、多路复用Selector
、Buffer
等暂时不用管,后面会讲到。
NIO 方式适用于链接数目多且链接比较短(轻操做)的架构,好比聊天服务器,并发局 限于应用中,编程复杂,JDK1.4 开始支持。同时,NIO
和普通IO的区别主要能够从存储数据的载体、是否阻塞等来区分:
Java AIO
与 NIO
不一样,当进行读写操做时,只须直接调用 API 的 read
或 write
方法便可。这两种方法均为异步的,对于读操做而言,当有流可读取时,操做系统会将可读的流传入 read
方 法的缓冲区,并通知应用程序;对于写操做而言,当操做系统将 write
方法传递的流写入完毕时,操做系统主动通知应用程序。便可以理解为,read/write
方法都是异步的,完成后会主动调用回调函数。在 JDK7
中,提供了异步文件通道和异步套接字通道的实现,这部份内容被称做 NIO
.
AIO
方式使用于链接数目多且链接比较长(重操做)的架构,好比相册服务器,充分调用 OS
参与并发操做,编程比较复杂,JDK7
开始支持。
目前来讲 AIO
的应用还不是很普遍,Netty
以前也尝试使用过 AIO
,不过又放弃了。
2、NIO核心组件介绍
1. Channel
在NIO
中,基本全部的IO操做都是从Channel
开始的,Channel
经过Buffer(缓冲区)
进行读写操做。
read()
表示读取通道中数据到缓冲区,write()
表示把缓冲区数据写入到通道。
Channel
有好多实现类,这里有三个最经常使用:
SocketChannel
:一个客户端发起TCP链接的ChannelServerSocketChannel
:一个服务端监听新链接的TCP Channel,对于每个新的Client链接,都会创建一个对应的SocketChannelFileChannel
:从文件中读写数据
其中SocketChannel
和ServerSocketChannel
是网络编程中最经常使用的,一会在最后的示例代码中会有讲解到具体用法。
2. Buffer
概念
Buffer
也被成为内存缓冲区,本质上就是内存中的一块,咱们能够将数据写入这块内存,以后从这块内存中读取数据。也能够将这块内存封装成NIO Buffer
对象,并提供一组经常使用的方法,方便咱们对该块内存进行读写操做。
Buffer
在java.nio
中被定义为抽象类:
咱们能够将Buffer
理解为一个数组的封装,咱们最经常使用的ByteBuffer
对应的数据结构就是byte[]
属性
Buffer
中有4个很是重要的属性:capacity、limit、position、mark
capacity
属性:容量,Buffer可以容纳的数据元素的最大值,在Buffer初始化建立的时候被赋值,并且不能被修改。
上图中,初始化Buffer的容量为8(图中从0~7,共8个元素),因此capacity = 8
-
limit
属性:表明Buffer可读可写的上限。- 写模式下:
limit
表明能写入数据的上限位置,这个时候limit = capacity
读模式下:在Buffer
完成全部数据写入后,经过调用flip()
方法,切换到读模式,此时limit
等于Buffer
中实际已经写入的数据大小。由于Buffer
可能没有被写满,因此limit<=capacity
- 写模式下:
-
position
属性:表明读取或者写入Buffer
的位置。默认为0。- 写模式下:每往
Buffer
中写入一个值,position
就会自动加1,表明下一次写入的位置。 - 读模式下:每往
Buffer
中读取一个值,position
就自动加1,表明下一次读取的位置。
- 写模式下:每往
从上图就能很清晰看出,读写模式下capacity、limit、position的关系了。
mark
属性:表明标记,经过mark()方法,记录当前position值,将position值赋值给mark,在后续的写入或读取过程当中,能够经过reset()方法恢复当前position为mark记录的值。
这几个重要属性讲完,咱们能够再来回顾下:
0 <= mark <= position <= limit <= capacity
如今应该很清晰这几个属性的关系了~
Buffer常见操做
建立Buffer
allocate(int capacity)
ByteBuffer buffer = ByteBuffer.allocate(1024); int count = channel.read(buffer);
例子中建立的ByteBuffer
是基于堆内存的一个对象。
wrap(array)
wrap
方法能够将数组包装成一个Buffer
对象:
ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes()); channel.write(buffer);
allocateDirect(int capacity)
经过allocateDirect
方法也能够快速实例化一个Buffer
对象,和allocate
很类似,这里区别的是allocateDirect
建立的是基于堆外内存的对象。
堆外内存不在JVM堆上,不受GC的管理。堆外内存进行一些底层系统的IO操做时,效率会更高。
Buffer写操做
Buffer
写入能够经过put()
和channel.read(buffer)
两种方式写入。
一般咱们NIO的读操做的时候,都是从Channel
中读取数据写入Buffer
,这个对应的是Buffer
的写操做。
Buffer读操做
Buffer
读取能够经过get()
和channel.write(buffer)
两种方式读入。
仍是同上,咱们对Buffer
的读入操做,反过来讲就是对Channel
的写操做。读取Buffer
中的数据而后写入Channel
中。
其余常见方法
rewind()
:重置position位置为0,能够从新读取和写入buffer,通常该方法适用于读操做,能够理解为对buffer的重复读。
public final Buffer rewind() { position = 0; mark = -1; return this; }
flip()
:很经常使用的一个方法,通常在写模式切换到读模式的时候会常常用到。也会将position设置为0,而后设置limit等于原来写入的position。
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
clear()
:重置buffer中的数据,该方法主要是针对于写模式,由于limit设置为了capacity,读模式下会出问题。
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
mark()&reset()
:mark()
方法是保存当前position
到变量mark
z中,而后经过reset()
方法恢复当前position
为mark
,实现代码很简单,以下:
public final Buffer mark() { mark = position; return this; } public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
经常使用的读写方法能够用一张图总结一下:
3. Selector
概念
Selector
是NIO中最为重要的组件之一,咱们经常说的多路复用器
就是指的Selector
组件。 Selector
组件用于轮询一个或多个NIO Channel
的状态是否处于可读、可写。经过轮询的机制就能够管理多个Channel,也就是说能够管理多个网络链接。
轮询机制
- 首先,须要将Channel注册到Selector上,这样Selector才知道须要管理哪些Channel
- 接着Selector会不断轮询其上注册的Channel,若是某个Channel发生了读或写的时间,这个Channel就会被Selector轮询出来,而后经过SelectionKey能够获取就绪的Channel集合,进行后续的IO操做。
属性操做
- 建立Selector
经过open()
方法,咱们能够建立一个Selector
对象。
Selector selector = Selector.open();
- 注册Channel到Selector中
咱们须要将Channel
注册到Selector
中,才可以被Selector
管理。
channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
某个Channel
要注册到Selector
中,那么该Channel必须是非阻塞,全部上面代码中有个configureBlocking()
的配置操做。
在register(Selector selector, int interestSet)
方法的第二个参数,标识一个interest
集合,意思是Selector对哪些事件感兴趣,能够监听四种不一样类型的事件:
public static final int OP_READ = 1 << 0; public static final int OP_WRITE = 1 << ; public static final int OP_CONNECT = 1 << 3; public static final int OP_ACCEPT = 1 << 4;
Connect事件
:链接完成事件( TCP 链接 ),仅适用于客户端,对应 SelectionKey.OP_CONNECT。Accept事件
:接受新链接事件,仅适用于服务端,对应 SelectionKey.OP_ACCEPT 。Read事件
:读事件,适用于两端,对应 SelectionKey.OP_READ ,表示 Buffer 可读。Write事件
:写时间,适用于两端,对应 SelectionKey.OP_WRITE ,表示 Buffer 可写。
Channel
触发了一个事件,代表该时间已经准备就绪:
- 一个Client Channel成功链接到另外一个服务器,成为“链接就绪”
- 一个Server Socket准备好接收新进入的接,称为“接收就绪”
- 一个有数据可读的Channel,称为“读就绪”
- 一个等待写数据的Channel,称为”写就绪“
固然,Selector
是能够同时对多个事件感兴趣的,咱们使用或运算便可组合多个事件:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
Selector其余一些操做
选择Channel
public abstract int select() throws IOException; public abstract int select(long timeout) throws IOException; public abstract int selectNow() throws IOException;
当Selector执行select()
方法就会产生阻塞,等到注册在其上的Channel准备就绪就会当即返回,返回准备就绪的数量。
select(long timeout)
则是在select()
的基础上增长了超时机制。 selectNow()
当即返回,不产生阻塞。
有一点很是须要注意: select
方法返回的 int
值,表示有多少 Channel
已经就绪。
自上次调用select
方法后有多少 Channel
变成就绪状态。若是调用 select
方法,由于有一个 Channel
变成就绪状态则返回了 1 ;
若再次调用 select
方法,若是另外一个 Channel
就绪了,它会再次返回1。
获取可操做的Channel
Set selectedKeys = selector.selectedKeys();
当有新增就绪的Channel
,调用select()
方法,就会将key添加到Set集合中。
3、代码示例
前面铺垫了这么多,主要是想让你们可以看懂NIO
代码示例,也方便后续你们来本身手写NIO
网络编程的程序。建立NIO服务端的主要步骤以下:
1. 打开ServerSocketChannel,监听客户端链接 2. 绑定监听端口,设置链接为非阻塞模式 3. 建立Reactor线程,建立多路复用器并启动线程 4. 将ServerSocketChannel注册到Reactor线程中的Selector上,监听ACCEPT事件 5. Selector轮询准备就绪的key 6. Selector监听到新的客户端接入,处理新的接入请求,完成TCP三次握手,创建物理链路 7. 设置客户端链路为非阻塞模式 8. 将新接入的客户端链接注册到Reactor线程的Selector上,监听读操做,读取客户端发送的网络消息 9. 异步读取客户端消息到缓冲区 10.对Buffer编解码,处理半包消息,将解码成功的消息封装成Task 11.将应答消息编码为Buffer,调用SocketChannel的write将消息异步发送给客户端
NIOServer.java
:
public class NIOServer { private static Selector selector; public static void main(String[] args) { init(); listen(); } private static void init() { ServerSocketChannel serverSocketChannel = null; try { selector = Selector.open(); serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(9000)); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("NioServer 启动完成"); } catch (IOException e) { e.printStackTrace(); } } private static void listen() { while (true) { try { selector.select(); Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator(); while (keysIterator.hasNext()) { SelectionKey key = keysIterator.next(); keysIterator.remove(); handleRequest(key); } } catch (Throwable t) { t.printStackTrace(); } } } private static void handleRequest(SelectionKey key) throws IOException { SocketChannel channel = null; try { if (key.isAcceptable()) { ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); channel = serverSocketChannel.accept(); channel.configureBlocking(false); System.out.println("接受新的 Channel"); channel.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int count = channel.read(buffer); if (count > 0) { System.out.println("服务端接收请求:" + new String(buffer.array(), 0, count)); channel.register(selector, SelectionKey.OP_WRITE); } } if (key.isWritable()) { ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put("收到".getBytes()); buffer.flip(); channel = (SocketChannel) key.channel(); channel.write(buffer); channel.register(selector, SelectionKey.OP_READ); } } catch (Throwable t) { t.printStackTrace(); if (channel != null) { channel.close(); } } } }
NIOClient.java
:
public class NIOClient { public static void main(String[] args) { new Worker().start(); } static class Worker extends Thread { @Override public void run() { SocketChannel channel = null; Selector selector = null; try { channel = SocketChannel.open(); channel.configureBlocking(false); selector = Selector.open(); channel.register(selector, SelectionKey.OP_CONNECT); channel.connect(new InetSocketAddress(9000)); while (true) { selector.select(); Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator(); while (keysIterator.hasNext()) { SelectionKey key = keysIterator.next(); keysIterator.remove(); if (key.isConnectable()) { System.out.println(); channel = (SocketChannel) key.channel(); if (channel.isConnectionPending()) { channel.finishConnect(); ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put("你好".getBytes()); buffer.flip(); channel.write(buffer); } channel.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int len = channel.read(buffer); if (len > 0) { System.out.println("[" + Thread.currentThread().getName() + "]收到响应:" + new String(buffer.array(), 0, len)); Thread.sleep(5000); channel.register(selector, SelectionKey.OP_WRITE); } } if(key.isWritable()) { ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put("你好".getBytes()); buffer.flip(); channel = (SocketChannel) key.channel(); channel.write(buffer); channel.register(selector, SelectionKey.OP_READ); } } } } catch (Exception e) { e.printStackTrace(); } finally{ if(channel != null){ try { channel.close(); } catch (IOException e) { e.printStackTrace(); } } if(selector != null){ try { selector.close(); } catch (IOException e) { e.printStackTrace(); } } } } } }
打印结果:
// Server端 NioServer 启动完成 接受新的 Channel 服务端接收请求:你好 服务端接收请求:你好 服务端接收请求:你好 // Client端 [Thread-0]收到响应:收到 [Thread-0]收到响应:收到 [Thread-0]收到响应:收到
4、总结
回顾一下使用 NIO
开发服务端程序的步骤:
- 建立
ServerSocketChannel
和业务处理线程池。 - 绑定监听端口,并配置为非阻塞模式。
- 建立
Selector
,将以前建立的ServerSocketChannel
注册到Selector
上,监听SelectionKey.OP_ACCEPT
。 - 循环执行
Selector.select()`` 方法,轮询就绪的
Channel`。 - 轮询就绪的
Channel
时,若是是处于OP_ACCEPT
状态,说明是新的客户端接入,调用ServerSocketChannel.accept
接收新的客户端。 - 设置新接入的
SocketChannel
为非阻塞模式,并注册到Selector
上,监听OP_READ
。 - 若是轮询的
Channel
状态是OP_READ
,说明有新的就绪数据包须要读取,则构造ByteBuffer
对象,读取数据。
那从这些步骤中基本知道开发者须要熟悉的知识点有:
jdk-nio
提供的几个关键类:Selector
,SocketChannel
,ServerSocketChannel
,FileChannel
,ByteBuffer
,SelectionKey
- 须要知道网络知识:tcp粘包拆包 、网络闪断、包体溢出及重复发送等
- 须要知道
linux
底层实现,如何正确的关闭channel
,如何退出注销selector
,如何避免selector
太过于频繁 - 须要知道如何让
client
端得到server
端的返回值,而后才返回给前端,须要如何等待或在怎样做熔断机制 - 须要知道对象序列化,及序列化算法
- 省略等等,由于我已经有点不舒服了,做为程序员的我习惯了舒舒服服简单的API,不用太知道底层细节,就能写出比较健壮和没有Bug的代码...
NIO 原生 API 的弊端 :
① NIO 组件复杂 : 使用原生 NIO
开发服务器端与客户端 , 须要涉及到 服务器套接字通道 ( ServerSocketChannel
) , 套接字通道 ( SocketChannel
) , 选择器 ( Selector
) , 缓冲区 ( ByteBuffer
) 等组件 , 这些组件的原理 和API 都要熟悉 , 才能进行 NIO
的开发与调试 , 以后还须要针对应用进行调试优化
② NIO 开发基础 : NIO
门槛略高 , 须要开发者掌握多线程、网络编程等才能开发而且优化 NIO
网络通讯的应用程序
③ 原生 API 开发网络通讯模块的基本的传输处理 : 网络传输不光是实现服务器端和客户端的数据传输功能 , 还要处理各类异常状况 , 如 链接断开重连机制 , 网络堵塞处理 , 异常处理 , 粘包处理 , 拆包处理 , 缓存机制 等方面的问题 , 这是全部成熟的网络应用程序都要具备的功能 , 不然只能说是入门级的 Demo
④ NIO BUG : NIO
自己存在一些 BUG , 如 Epoll
, 致使 选择器 ( Selector
) 空轮询 , 在 JDK 1.7 中尚未解决
Netty
在 NIO
的基础上 , 封装了 Java 原生的 NIO API
, 解决了上述哪些问题呢 ?
相比 Java NIO,使用 Netty
开发程序,都简化了哪些步骤呢?...等等这系列问题也都是咱们要问的问题。不过由于这篇只是介绍NIO
相关知识,没有介绍Netty API
的使用,因此介绍Netty API
使用简单开发门槛低等优势有点站不住脚。那么就留到后面跟你们一块儿开启Netty
学习之旅,探讨人人说好的Netty
究竟是不是江湖传言的那么好。
一块儿期待后续的Netty
之旅吧!