摘抄:【深入理解Java】一篇文章带你彻底吃透Java NIO_哪 吒的博客-优快云博客_深入理解java
一、I/O模型
I/O模型的本质是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能。
Java共支持三种网络编程模型:BIO、NIO、AIO
BIO:同步并阻塞,服务实现模式为一个连接一个线程,即客户端有一个连接请求时,服务端就需要启动一个线程进行处理。
NIO: 同步非阻塞,服务器实现模式为一个线程处理多个请求连接,即客户端发送的请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理。
AIO:异步非阻塞,AIO引入异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端。
二、BIO、NIO、AIO应用场景
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高, 并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕 系统,服务器间通讯等。编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分 调用OS参与并发操作,编程比较复杂,JDK7开始支持
三、BIO与NIO比较
BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多;
BIO 是阻塞的,NIO 则是非阻塞的;
BIO基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进 行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因 此使用单个线程就可以监听多个客户端通道。
四、NIO核心
NIO 有三大核心部分:Selector(选择器)、Channel(通道)、Buffer(缓冲区)。
NIO是面向缓冲区,或者说面向块编程,数据读取到一个 它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就 增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求 的数量比HTTP1.1大了好几个数量级。
简而言之,NIO可以一个线程处理多个请求。
NIO 三大核心原理示意图
五、Selector(选择器)
1、基本介绍
Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连 接,就会使用到Selector(选择器)。
Selector 能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然 后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个 通道,也就是管理多个连接和请求。
只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少 了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。
避免了多线程之间的上下文切换导致的开销。
liue 、通过NIO实现简单的服务端客户端通信
1、服务端
package com.nezha.guor.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class NioServer {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private static final int PORT = 8080;
public NioServer() {
try {
//获得选择器
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞模式
serverSocketChannel.configureBlocking(false);
//将该ServerSocketChannel 注册到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch (IOException e) {
System.out.println("NioServer error:"+e.getMessage());
}
}
public void listen() {
System.out.println("监听线程启动: " + Thread.currentThread().getName());
try {
while (true) {
int count = selector.select();
if(count > 0) {
//遍历得到selectionKey集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if(key.isAcceptable()) {
SocketChannel sc = serverSocketChannel.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
System.out.println(sc.getRemoteAddress() + " 上线 ");
}
//通道发送read事件,即通道是可读的状态
if(key.isReadable()) {
getDataFromChannel(key);
}
//当前的key 删除,防止重复处理
iterator.remove();
}
} else {
System.out.println("等待中");
}
}
}catch (Exception e) {
System.out.println("listen error:"+e.getMessage());
}
}
private void getDataFromChannel(SelectionKey key) {
SocketChannel channel = null;
try {
channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);
//根据count的值做处理
if(count > 0) {
String msg = new String(buffer.array());
System.out.println("来自客户端: " + msg);
//向其它的客户端转发消息(排除自己)
sendInfoToOtherClients(msg, channel);
}
}catch (IOException e) {
try {
System.out.println(channel.getRemoteAddress() + " 离线了");
//取消注册
key.cancel();
}catch (IOException ex) {
System.out.println("getDataFromChannel error:"+ex.getMessage());
}
}finally {
try {
channel.close();
}catch (IOException ex) {
System.out.println("channel.close() error:"+ex.getMessage());
}
}
}
//转发消息给其它客户(通道)
private void sendInfoToOtherClients(String msg, SocketChannel self ) throws IOException{
System.out.println("服务器转发消息中...");
System.out.println("服务器转发数据给客户端线程: " + Thread.currentThread().getName());
//遍历 所有注册到selector 上的 SocketChannel,并排除 self
for(SelectionKey key: selector.keys()) {
Channel targetChannel = key.channel();
//排除自己
if(targetChannel instanceof SocketChannel && targetChannel != self) {
SocketChannel dest = (SocketChannel)targetChannel;
//将信息存储到buffer
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//将buffer数据写入通道
dest.write(buffer);
}
}
}
public static void main(String[] args) {
//创建服务器对象
NioServer nioServer = new NioServer();
nioServer.listen();
}
}
2、客户端
package com.nezha.guor.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
public class NioClient {
private final int PORT = 8080; //服务器端口
private Selector selector;
private SocketChannel socketChannel;
private String username;
public NioClient() throws IOException {
selector = Selector.open();
socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
//设置非阻塞
socketChannel.configureBlocking(false);
//将channel注册到selector
socketChannel.register(selector, SelectionKey.OP_READ);
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + " is ok...");
}
//向服务器发送消息
public void sendInfo(String info) {
info = username + " 说:" + info;
try {
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
}catch (IOException e) {
System.out.println("sendInfo error:"+e.getMessage());
}
}
//读取从服务器端回复的消息
public void readInfo() {
try {
int readChannels = selector.select();
if(readChannels > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if(key.isReadable()) {
//得到相关的通道
SocketChannel sc = (SocketChannel) key.channel();
//得到一个Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取
sc.read(buffer);
//把读到的缓冲区的数据转成字符串
String msg = new String(buffer.array());
System.out.println(msg.trim());
}
}
iterator.remove(); //删除当前的selectionKey, 防止重复操作
} else {
System.out.println("没有可以用的通道...");
}
}catch (Exception e) {
System.out.println("readInfo error:"+e.getMessage());
}
}
public static void main(String[] args) throws Exception {
NioClient nioClient = new NioClient();
new Thread() {
public void run() {
while (true) {
nioClient.readInfo();
try {
Thread.currentThread().sleep(2000);
}catch (InterruptedException e) {
System.out.println("sleep error:"+e.getMessage());
}
}
}
}.start();
//发送数据给服务器端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
nioClient.sendInfo(scanner.nextLine());
}
}
}