开发者学堂课程【大数据 ZooKeeper 快速入门: 网络编程:IO 通信模型—BIO&;NIO】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/549/detail/7583
网络编程:IO 通信模型—BIO&;NIO
内容介绍
一、IO 通信模型
二、BIO (阻塞模式)
三、NIO(非阻塞模式)
一、IO 通信模型
网络通信的本质是网络间的数据IO。只要有I0,就会有阻塞或非阻塞的问题,无论这个 IO 是网络的,还是硬盘的。原因在于程序是运行在系统之上的,任何形式的 I0 操作发起都需要系统的支持。
网络通信本质上是两台机器之间的数据传输,也就是数据的 IO,I 表示 input,O 表示 output。有了输入和输出才构成通信的本质。
二、BIO(阻塞模式)
BIO 即 blocking I0,是一种阻塞式的I0。
jdkl.4版本之前 Socket 即 BIO 模式。
在 Socket 编程中,在服务端设置了 Socket 的启动后有一个方法叫 accept()方法,监听客户端有没有请求链接,因此是一个阻塞的。
如果用 read 方法去读数据,客户端不发送数据,那么 read 方法也会阻塞,因此这两个方法都是所谓的阻塞点。
BIO 的问题在于 accept()、 read )的操作点都是被阻塞的。
服务器线程发起一个 accept 动作,按照通常的理解认为是直接去监听端口,监听一下有没有客户端 socket 方法发生链接请求,实际上询问操作系统是否有新的 socket 信息从端口 X 发送过来。
注意,是询问操作系统,因为任何请求都需要操作系统的支持。
如果有就相应生成 socket 进行通信,如果操作系统没有发现有 socket 从指定的端口X来,那么操作系统就会等待。这样 serverSocket. accept() 方法就会一直等待。这就是为什么 accept() 方法为什么会阻塞。
如果想让 BIO 同时处理多个客户端请求,就必须使用多线程,即每次 accept 阻塞等待来自客户端请求,一旦收到连接请求就建立通信,同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理。
比如现在有一个客户端发送了一个连接请求,服务端 socket 通过操作系统感受到这个连接请求,然后就会接收,接收后 new 一个新的线程生成一个 socket 完成通信,之后主线程 accept()返回来继续等待新的连接请求。
通过这种方式可以在某种更程度上提高 BIO 编程模型效率,不管有多少个客户端连接请求,服务端都去接收,接收后生成相应的线程完成通信。
虽然每一个 socket 都以一个线程相对应,还有有阻塞点存在,也就是说 accept ()接收时还是一个一个接收。
比如当这三个客户端同时发起连接请求,accept()只能同时处理一个,接收一个new 一个线程,所以 BIO 模式多线程可以在某种上提高效率,但还没有彻底改变,因此把 BIO 叫做阻塞模式。
三、NIO(非阻塞模式)
NIO 即 non-blocking I0,是一种非阻塞式的 I0。jdk1.4 之后提供。
NIO 三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)。
Buffer:容器对象,包含一些要写入或者读出的数据。
在 NIO 库,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。
任何时候访问 NI0 中的数据,都是通过缓冲区进行操作。
在 NIO 模式下写数据要写在 buffer 容器中,读取数据也要从 buffer 容器中读取。不在直接面对数据,而是直接面对容器对象 buffer,在 buffer 存在所需要的数据。因此把 buffer 叫做缓冲区,是数据接触的直接点。
Channel:通道对象,对数据的读取和写入要通过 Channel,它就像水管一样。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。Channel 不会直接处理字节数据,而是通过 Buffer 对象来处理数据。
在水管中数据既可以输入,也可以输出,也就是说它的传输是双向的,不管是传统的 inputsream 还是 outputstream 都是单方向的,inputsream 只能往里面读取数据,outputstream 只能往外写数据。
Channel 里面不会直接存放数据,结合 buffer 特点,应该在 Channel 中传输的是一个个容器对象 buffer,buffer 里包装着各种各样的数据。
Selector:多路复用器,选择器。提供选择已经就绪的任务的能力。
Selector会不断轮询注册在其上的 Channel,如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,进行后续的 I/0 操作。
这样服务器只需要一两个线程就可以进行多客户端通信。
在 NIO 编程模式下,数据两方不在直接接触,比如 thread 上面是一个服务端线程,下面各个客户端要想和 thread 进行数据通信,首先需要在 selector 注册 channel,这里可以将 selector 理解为服务端管家,不按时不管是什么客户端建立什么通信,首先现在 selector 注册 channel。之后逐个轮询各个 channel。
比如一个 channel 已经准备好了,那么 selector 选择器就可以将它选择出来将它与服务端之间的通道打开,因为 selector 具备选择已经准备就绪的任务的能力。
在这种模式下想要满足多线程模式通信,只需要在客户端简单的一两个线程就可以,因为服务端不在与客户端直接通信,而是通过 selector 进行通信。