Java中的IO、NIO、AIO:
BIO:在Java1.4之前,我们建立网络连接均使用BIO,属于同步阻塞IO。默认情况下,当有一条请求接入就有一条线程专门接待。所以,在客户端向服务端请求时,会询问是否有空闲线程进行接待,如若没有则一直等待或拒接。当并发量小时还可以接受,当请求量一多起来则会有许多线程生成,在Java中,多线程的上下文切换会消耗计算机有限的资源和性能,造成资源浪费。
NIO:NIO的出现是为了解决再BIO下的大并发量问题。其特点是能用一条线程管理所有连接。如下图所示:
NIO是同步非阻塞模型,通过一条线程控制选择器(Selector)来管理多个Channel,减少创建线程和上下文切换的浪费。当线程通过选择器向某条Channel请求数据但其没有数据时,是不会阻塞的,直接返回,继续干其他事。而一旦某Channel就绪,线程就能去调用请求数据等操作。当该线程对某条Channel进行写操作时同样不会被阻塞,所以该线程能够对多个Channel进行管理。
NIO是面向缓冲流的,即数据写入写出都是通过 Channel —— Buffer 这一途径。(双向流通)
AIO:与之前两个IO模型不同的是,AIO属于异步非阻塞模型。当进行读写操作时只须调用api的read方法和write方法,这两种方法均是异步。对于读方法来说,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。换言之就是当调用完api后,操作系统完成后会调用回调函数。
总结:一般IO分为同步阻塞模型(BIO),同步非阻塞模型(NIO),异步阻塞模型,异步非阻塞模型(AIO)
同步阻塞模型指的是当调用io操作时必须等到其io操作结束
同步非阻塞模型指当调用io操作时不必等待可以继续干其他事,但必须不断询问io操作是否完成。
异步阻塞模型指应用调用io操作后,由操作系统完成io操作,但应用必须等待或去询问操作系统是否完成。
异步非阻塞指应用调用io操作后,由操作系统完成io操作并调用回调函数,应用完成放手不管。
NIO的小Demo之服务端
首先,先看下服务端的大体代码
1 | 复制代码public class ServerHandle implements Runnable{ |
首先我们先看看该服务端的构造函数的实现:
1 | 复制代码public ServerHandle(int port){ |
在这里创建了选择器和监听通道,并将该监听通道注册到选择器上并选择其感兴趣的事件(accept)。后续其他接入的连接都将通过该 监听通道 传入。
然后就是写方法的实现:
1 | 复制代码 private void doWrite(SocketChannel channel, String response) throws IOException { |
其次是当由事件传入时,即对连接进来的链接的处理方法
1 | 复制代码 private void handleInput(SelectionKey key) throws IOException{ |
这里要说明的是,只要ServerSocketChannel及SocketChannel向Selector注册了特定的事件,Selector就会监控这些事件是否发生。
如在构造方法中有一通道serverSocketChannel注册了accept事件。当其就绪时就可以通过调用selector的selectorKeys()方法,访问”已选择键集“中的就绪通道。
压轴方法:
1 | 复制代码 @Override |
此方法为服务端的主体方法。大致流程如下:
- 打开ServerSocketChannel,监听客户端连接
- 绑定监听端口,设置连接为非阻塞模式(阻塞模式下不能注册到选择器)
- 创建Reactor线程,创建选择器并启动线程
- 将ServerSocketChannel注册到Reactor线程中的Selector上,监听ACCEPT事件
- Selector轮询准备就绪的key
- Selector监听到新的客户端接入,处理新的接入请求,完成TCP三次握手,简历物理链路
- 设置客户端链路为非阻塞模式
- 将新接入的客户端连接注册到Reactor线程的Selector上,监听读操作,读取客户端发送的网络消息
异步读取客户端消息到缓冲区 - 调用write将消息异步发送给客户端
NIO的小Demo之客户端
1 | 复制代码public class ClientHandle implements Runnable{ |
首先先看构造函数的实现:
1 | 复制代码 public ClientHandle(String ip,int port) { |
接下来看对就绪通道的处理办法:
1 | 复制代码 private void handleInput(SelectionKey key) throws IOException{ |
在run方法之前需先看下此方法的实现:
1 | 复制代码 private void doConnect() throws IOException{ |
当SocketChannel工作于非阻塞模式下时,调用connect()时会立即返回:
如果连接建立成功则返回的是true(比如连接localhost时,能立即建立起连接),否则返回false。
在非阻塞模式下,返回false后,必须要在随后的某个地方调用finishConnect()方法完成连接。
当SocketChannel处于阻塞模式下时,调用connect()时会进入阻塞,直至连接建立成功或者发生IO错误时,才从阻塞状态中退出。
所以该代码在connect服务端后返回false(但还是有作用的),并在else语句将该通道注册在选择器上并选择connect事件。
客户端的run方法:
1 | 复制代码 @Override |
发送信息到服务端的方法:
1 | 复制代码 public void sendMsg(String msg) throws Exception{ |
完整代码:
服务端:
1 | 复制代码/** |
服务端主体:
1 | 复制代码import java.io.IOException; |
客户端:
1 | 复制代码/** |
客户端主体代码:
1 | 复制代码import java.io.IOException; |
测试类:
1 | 复制代码import java.util.Scanner; |
控制台打印:
1 | 复制代码服务器已启动,端口号为:12345 |
如有不妥之处,请告诉我。
本文转载自: 掘金