主要阐述点:

1、同步/异步 or  阻塞/非阻塞

2、网络模型演进

3、NIO代码示例

一、同步/异步 or  阻塞/非阻塞

同步/异步:核心点在于是否等待结果返回。同步即调用者必须等到结果才返回,而异步则可立即返回无需等待结果,通过后期异步回调、状态检查等方式得到结果。

阻塞/非阻塞:核心点在于执行线程是否会阻塞。阻塞,例如在读操作中如果内核数据未准备好则会当阻塞读线程;而非阻塞,在内核数据未准备好前读线程无需等待,可以忙里偷闲干别的事情,

但是需要定期检查。

二、网络模型演进

1、原始版BIO: 单线程监听链接,每次只处理一个链接请求且读写阻塞。缺点:每次只能处理一个请求,读写阻塞。

2、线程版BIO: 单线程监听链接,可同时处理多个请求,每次分配一个线程处理一个链接请求,线程之间读写非阻塞。缺点:线程可能开设过多导致机器瓶颈,线程过多导致cpu上下文切换消耗大,

线程销毁浪费资源,单线程内部读写依然阻塞。

3、线程池版BIO: 单线程监听链接,可同时处理多个请求,每次将链接请求加入线程池工作队列,减少【线程版BIO】线程创建过多问题,线程之间读写非阻塞。缺点:存在客户链接数量限制,

单线程内部读写依然阻塞。

4、JDK4版本NIO:单线程监听链接,可同时处理多个请求,基于事件驱动形式。Selector封装操作系统调用(如linux epoll),每个客户端链接用通道Channel表示,当通道Channel数据准备完毕将触发相应事件。

  • 4.1、NIO解决传统BIO痛点问题:

(1)读写阻塞:传统BIO只有当线程读写完成才会返回,否则线程将阻塞等待。而NIO在通道Channel准备完毕之后会由Selector触发事件,线程基于事件完成相应操作。在内核数据未准备好之前,线程

可以忙里偷闲处理其他逻辑,解决BIO阻塞等待内核数据准备的问题。

(2)客户端链接数量限制:BIO利用开设线程解决客户端之间读写非阻塞问题,但单机开设线程数量存在限制(即使开设线程池处理也有上限),而在像QQ聊天室这样需要建立大量长链接但数据量小的场景中难以满足需求。

在NIO中每个客户端链接对应一个SocketChannel,所有通道Channel注册到Selector统一管理。通道Channel相对线程Thread更轻量级,单机即可同时处理大量链接。

  • 4.2、网络模型

上图体现了JAVA NIO 中有3个核心概念:

  • Channel:与传统BIO的InputStream/OutputStream类似,区别在于Channel为双向通道支持同时读写。
  • Buffer:独立数据缓冲区,所有关于Channel的读写操作都需要经过Buffer。
  • Selector:将Channel注册到Selector并监听通道事件,是NIO模型中的核心类。

详细概念参考 JAVA NIO Tutorial

三、NIO代码示例

服务端Server端代码:

public class MyNioServer {
private Selector selector;
private final static int port = 8686;
private final static int BUF_SIZE = 10240;
private static ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); private void initServer() throws IOException {
//创建通道管理器对象selector
this.selector = Selector.open(); //创建一个通道对象channel
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
channel.socket().bind(new InetSocketAddress(port));
channel.register(selector, SelectionKey.OP_ACCEPT); while (true){
// 这是一个阻塞方法,一直等待直到有数据可读,返回值是key的数量(可以有多个)
selector.select();
// 如果channel有数据了,将生成的key访入keys集合中
Set<SelectionKey> keys = selector.selectedKeys();
// 得到这个keys集合的迭代器
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()){
doAccept(key);
}else if (key.isReadable()){
doRead(key);
}else if (key.isWritable()){
doWrite(key);
}else if (key.isConnectable()){
System.out.println("连接成功!");
}
}
selector.selectedKeys().clear();
}
} public void doAccept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
System.out.println("ServerSocketChannel正在循环监听");
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(key.selector(), SelectionKey.OP_READ);
} public void doRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
byteBuffer.clear() ;
int size = clientChannel.read(byteBuffer);
byteBuffer.flip() ;
byte[] data = byteBuffer.array();
String msg = new String(data, 0, size).trim();
System.out.println("从客户端发送过来的消息是:"+msg); clientChannel.register(selector, SelectionKey.OP_WRITE);
} public void doWrite(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
byteBuffer.clear();
byteBuffer.put("收到你的请求 给客户端回复消息".getBytes()) ;
byteBuffer.flip() ;
while (byteBuffer.hasRemaining()){
clientChannel.write(byteBuffer);
} clientChannel.register(selector, SelectionKey.OP_READ);
} public static void main(String[] args) throws IOException {
MyNioServer myNioServer = new MyNioServer();
myNioServer.initServer();
}
}

MyNioServer.java

客户端Client代码:

public class MyNioClient {
private Selector selector; //创建一个选择器
private final static int port_server = 8686;
private final static int BUF_SIZE = 10240;
private static ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); private void initClient() throws IOException {
this.selector = Selector.open();
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress(port_server));
clientChannel.register(selector, SelectionKey.OP_CONNECT); Scanner scanner = new Scanner(System.in);
while (true){
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if (key.isConnectable()){
doConnect(key);
}else if (key.isWritable()){
doWrite(key, scanner);
}else if (key.isReadable()){
doRead(key);
}
} selector.selectedKeys().clear();
}
} public void doConnect(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
if (clientChannel.isConnectionPending()){
clientChannel.finishConnect();
}
System.out.println("已经与服务端建立链接");
clientChannel.register(selector, SelectionKey.OP_WRITE);
} public void doWrite(SelectionKey key, Scanner scanner) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
System.out.print("please input message:");
String message = scanner.nextLine();
byteBuffer.clear();
byteBuffer.put(message.getBytes("UTF-8"));
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
clientChannel.write(byteBuffer);
} clientChannel.register(selector, SelectionKey.OP_READ);
} public void doRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
byteBuffer.clear() ;
int size = clientChannel.read(byteBuffer);
byteBuffer.flip() ;
byte[] data = byteBuffer.array();
String msg = new String(data, 0 , size).trim();
System.out.println("服务端发送消息:"+msg); clientChannel.register(selector, SelectionKey.OP_WRITE);
} public static void main(String[] args) throws IOException {
MyNioClient myNioClient = new MyNioClient();
myNioClient.initClient();
}
}

MyNioClient.java

交互信息:

==> 客户端 

已经与服务端建立链接
please input message:hello
服务端发送消息:收到你的请求 给客户端回复消息
please input message:world
服务端发送消息:收到你的请求 给客户端回复消息
please input message:kitty
服务端发送消息:收到你的请求 给客户端回复消息
please input message: ==> 服务端
ServerSocketChannel正在循环监听
从客户端发送过来的消息是:hello
从客户端发送过来的消息是:world
从客户端发送过来的消息是:kitty

参看链接:深入分析 Java I/O 的工作机制

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

最新文章

  1. PowerShell过滤文件中的重复内容
  2. servlet中service() 和doGet() 、doPost() 学习笔记
  3. 导入dmp文件时的注意事项
  4. Angular JS 学习之Http
  5. jquery点击添加样式,再点击取出样式
  6. 设计模式C#实现(七)——生成器模式
  7. Oracle性能优化--AUTOTRACE 操作
  8. google(转帖)
  9. [C# 基础知识系列]专题九: 深入理解泛型可变性
  10. 基于LINUX的多功能聊天室
  11. 第四十四节,subprocess系统命令模块
  12. SQL根据B表内容修改A表内容,查询表中重复记录,删除掉重复项只保留一条
  13. Oracle E-Business Suite Maintenance Guide Release 12.2(Patching Procedures)
  14. Java核心技术第四章——1.封装性
  15. 如何快速连接无线Wifi 使用二维码
  16. 8.2 GOF设计模式一: 单实例模式 SingleTon
  17. JavaSE | IO流
  18. 自学Zabbix2.4-web页面配置zabbix
  19. JMeter学习(二)录制脚本(转载)
  20. G1收集器

热门文章

  1. Jenkins插件、war下载地址
  2. 各种实用的 PHP 开源库推荐【转】
  3. 18 Ajax、Json以及jackson框架解析json的基本应用
  4. 常用Tables控件介绍(一)
  5. Delphi百度语音【支持语音识别和语音合成】
  6. Ubuntu18.04防火墙相关
  7. 基于工作组消息队列高可用&amp;msmq-wcf故障
  8. 学了python能干什么
  9. 【洛谷 P1659】 [国家集训队]拉拉队排练(manacher)
  10. whois