《Netty权威指南》第2章 NIO入门

本章是对JDK的BIO、NIO和JDK1.7最新提供的NIO2.0的使用进行说明。


2.1 传统的BIO编程
网络编程的基本模型是Client/Server模型。服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,成功的话,双方通过网络套接字(Socket)进行通信。


2.1.2 TImeServer源码分析
如果端口合法且没有被占用,服务端监听成功。


server=new ServerSocket(por
t);//服务端监听
while(true){
socket=server.accept();//如果没有客户端接入,阻塞在此
new Thread(new TimeServerHandler(socket).start);//有客户端接入时,创建新的客户端线程处理Socket链路
}

2.1.3 TimeClient分析
客户端通过Socket创建,发送查询时间服务器的指令,然后读取服务器端的响应并将结果打印出来,随后关闭连接,释放资源,退出执行。

我们发现,BIO主要的问题在于每当有一个新的客户端请求接入时,服务端必须创建一个新的线程处理新接入的客户端链路。
为了改进一对一连接模型,演进了通过线程池或消息队列处理N个客户端的模型。但底层通信机制仍使用同步阻塞IO,称为“伪异步”。


2.2 伪异步IO编程
客户端数M:线程池最大线程N的比例关系。M可以远远大于N。(同学和宿管阿姨的关系)

2.2.2 伪异步IO的TimeServer源码分析

TimeServerHandlerExecutePool singleExecutor =new TimeServerHandlerExecutePool(50,10000);
while(true){
socket=server.accept();
singleExecutor.execute(new TimeServerHanlder(socket));
}

创建一个时间服务器的线程池,当接收到新的客户端连接时,将请求Socket封装成一个Task,然后调用线程池的execute方法,避免了每个请求接入都创建一个新的线程。但底层的通信仍是同步阻塞模型,无法从根本上解决问题。

2.2.3 伪异步IO弊端分析
当对Socket的输入流进行读取操作时,它会一直阻塞,直到:有数据可读,可用数据已经读取完毕,发生空指针或者IO异常

写输出流时,也将被阻塞,直到所有要发送的字节全部写入完毕,或者发生异常。
读和写都是同步阻塞的,阻塞的时间取决于对方IO线程的处理速度和网络IO的传输速度。


2.3 NIO编程
与Socket和ServerSocket类对应,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。
1.缓冲区Buffer
Buffer是一个对象,它包含游戏额要写入或者读出的数据。
NIO库中,所有数据都是用缓冲区处理的。它实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但它不仅是一个数组,提供了对数据的结构化访问以及维护读写位置等信息。每个Java基本类型都对应一种缓冲区。
每个Buffer类都是Buffer接口的一个子实例。除了ByteBuffer,每一个Buffer类都有完全一样的操作。

2.通道Channel
Channel是一个通道,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的。一个流必须是InputStream或OutputStream的子类。而通道可以读写一同进行。
Channel实际分为两大类:用于网络读写的SelectableChannel和用于文件操作的FileChannel。

3.多路复用器Selector(把其比作手机,Channel比作闹钟)
它是JavaNIO的基础,提供选择已经就绪的任务的能力。简单来讲,Selector会不断轮询注册在其上的Channel,Channel发生读或写时,Channel就处于就绪状态,通过SelectionKey可以获取就绪Channel的集合,进行后续的IO操作。

一个多路复用器可以轮询多个Channel,由于JDK使用了epoll()代替传统的select实现。所以它并没有最大连接句柄1024/2048的限制。
这就意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。
这里写图片描述

NIO服务端的主要创建过程:

  1. 打开ServerSocketChannel,监听客户端的连接,它是所有客户端连接的父管道
  2. 绑定监听接口,设置连接为非阻塞模式
  3. 创建Reactor线程,创建selector并启动线程
Selector selector=Selector.open();
New Thread(new ReactorTask()).start();

1. 将ServerSocketChannel注册到Reactor线程的Selector上,监听ACCEPT事件。
- selector在线程run方法的无限循环体内轮询准备就绪的Key。
- selector监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路。

SocketChannel channel=svrChannel.accept();
  • 设置客户端链路为非阻塞模式
  • 新接入的客户端连接注册到reactor线程的selector上,监听读操作。读取客户端发送的网络消息。
  • 异步读取客户端请求消息到缓冲区
  • 对于ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排。
  • 将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端。


    2.6.2 为什么选择Netty
    优点:

  • API使用简单,开发门槛低
  • 功能强大,预置了多种编解码功能,支持多种主流协议
  • 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展
  • 性能高。成熟稳定
  • 社区活跃, 版本迭代周期短

猜你喜欢

转载自blog.csdn.net/qq_24572475/article/details/82316348