多路复用IO与NIO
最近在学习NIO相关知识,发现需要掌握的知识点非常多,当做笔记记录就下。
在学NIO之前得先去了解IO模型
(1)同步阻塞IO(Blocking IO):即传统的IO模型。
(2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
(3)多路复用IO(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
(4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。
这里重点介绍多路复用IO模型(JAVA NIO就是采用此模式)
在多路复用IO模型中,会有一个线程(Java中的Selector)不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
IO多路复用模型使用了Reactor设计模式实现了这一机制。Reactor模式有三种实现方式:
Reactor单线程
每个客户端发起连接请求都会交给acceptor,acceptor根据事件类型交给线程handler处理,注意acceptor 处理和 handler 处理都在一个线程中处理,所以其中某个 handler 阻塞时, 会导致其他所有的 client 的 handler 都得不到执行, 并且更严重的是, handler 的阻塞也会导致整个服务不能接收新的 client 请求(因为 acceptor 也被阻塞了). 因为有这么多的缺陷, 因此单线程Reactor 模型用的比较少.
Reactor多线程模式
有专门一个线程, 即 Acceptor 线程用于监听客户端的TCP连接请求.
客户端连接的 IO 操作都是由一个特定的 NIO 线程池负责. 每个客户端连接都与一个特定的 NIO 线程绑定, 因此在这个客户端连接中的所有 IO 操作都是在同一个线程中完成的.
客户端连接有很多, 但是 NIO 线程数是比较少的, 因此一个 NIO 线程可以同时绑定到多个客户端连接中.
缺点:如果我们的服务器需要同时处理大量的客户端连接请求或我们需要在客户端连接时, 进行一些权限的检查, 那么单线程的 Acceptor 很有可能就处理不过来, 造成了大量的客户端不能连接到服务器.
Reactor主从模式
Reactor 的主从多线程模型和 Reactor 多线程模型很类似, 只不过 Reactor 的主从多线程模型的 acceptor 使用了线程池来处理大量的客户端请求.
NIO代码层面是如何实现这三种模式呢?
acceptor :也可以理解为一个Handler,这个Handler只负责创建具体处理IO请求的Handler(负责所有client的连接请求),如果Reactor广播时SelectionKey创建一个Handler负责绑定相应的SocketChannel到Selector中。下次再次有IO事件时会调用对应的Handler去处理
/**
* 单独一个线程去处理链接请求
* Created by zhangwentao on 2018/4/12.
*/
public class Acceptor implements Runnable{
Reactor reactor;
public Acceptor(Reactor reactor){
this.reactor=reactor;
} public void run() {
try {
//监听TCP链接请求
SocketChannel socketChannel=reactor.serverSocketChannel.accept();
if(socketChannel!=null)//调用Handler来处理channel
new SocketReadHandler(reactor.selector, socketChannel);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Reactor 的作用 :给ServerSocketChannel设置一个Acceptor,接收请求,给每一个一个SocketChannel(代表一个Client)关联一个Handler , 要注意其实Acceptor也是一个Handler(只是与它关联的channel是ServerSocketChannel而不是SocketChannel)
代码如下
/**
* Reactor模式有助于理解netty
* Created by zhangwentao on 2018/4/12.
*/
public class Reactor implements Runnable {
public final Selector selector;
public final ServerSocketChannel serverSocketChannel; public Reactor(int port) throws IOException {
//用于监控fds
selector=Selector.open();
//socket服务器的chanel
serverSocketChannel=ServerSocketChannel.open(); InetSocketAddress inetSocketAddress=new InetSocketAddress(InetAddress.getLocalHost(),port);
//
serverSocketChannel.socket().bind(inetSocketAddress);
//不设置阻塞队列
serverSocketChannel.configureBlocking(false); //向selector注册该channel 返回selectionKey
SelectionKey selectionKey=serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //利用selectionKey的attache功能绑定Acceptor 如果有事情,触发Acceptor
selectionKey.attach(new Acceptor(this));
} public void run() {
try {
while(!Thread.interrupted()){
selector.select();//selector 阻塞
Set<SelectionKey> selectionKeys= selector.selectedKeys();
Iterator<SelectionKey> it=selectionKeys.iterator();
//Selector如果发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。
while(it.hasNext()){
//来一个事件 第一次触发一个accepter线程
//以后触发SocketReadHandler
SelectionKey selectionKey=it.next();
dispatch(selectionKey);
selectionKeys.clear();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 运行Acceptor或SocketReadHandler
* @param key
*/
void dispatch(SelectionKey key) {
Runnable r = (Runnable)(key.attachment());
if (r != null){
r.run();
}
}
}
hanler线程是具体的事件处理者,例如ReadHandler、SendHandler,ReadHandler负责读取缓存中的数据,然后再调用一个工作处理线程去处理读取到的数据。具体为一个SocketChannel,Acceptor初始化该Handler时会将SocketChannel注册到Reactor的Selector中,同时将SelectionKey绑定该Handler,这样下次就会调用本Handler。代码如下
**
* Created by zhangwentao on 2018/4/12.
*/
public class SocketReadHandler implements Runnable {
private SocketChannel socketChannel;
public SocketReadHandler(Selector selector, SocketChannel socketChannel) throws IOException{
this.socketChannel=socketChannel;
socketChannel.configureBlocking(false); SelectionKey selectionKey=socketChannel.register(selector, 0); //将SelectionKey绑定为本Handler 下一步有事件触发时,将调用本类的run方法。
//参看dispatch(SelectionKey key)
selectionKey.attach(this); //同时将SelectionKey标记为可读,以便读取。
selectionKey.interestOps(SelectionKey.OP_READ);
selector.wakeup();
} /**
* 处理读取数据
*/
public void run() {
ByteBuffer inputBuffer=ByteBuffer.allocate(1024);
inputBuffer.clear();
try {
socketChannel.read(inputBuffer);
//激活线程池 处理这些request
//requestHandle(new Request(socket,btt));
} catch (IOException e) {
e.printStackTrace();
}
}
上述就是用原生的NIO实现reactor模式,我们发现还是有些繁琐的(多线程都没有写进去)
用netty可以很方便的实现三种方式,单线程模式:
Bootstrap b = new Bootstrap();
EventLoopGroup eventLoopGroup=new NioEventLoopGroup(1);
b.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.remoteAddress(serverAddress);
我们观察EventLoopGroup的构造方法 EventLoopGroup的参数表示线程池大小(1表示只有一个线程),Bootstrap.group
多线程模式
Bootstrap b = new Bootstrap();
EventLoopGroup acceptorLoopGroup=new NioEventLoopGroup(1);
EventLoopGroup handlerLoopGroup=new NioEventLoopGroup();
b.group(eventLoopGroup,handlerLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.remoteAddress(serverAddress);
EventLoopGroup acceptorLoopGroup=new NioEventLoopGroup(1);说明acceptor还是单线程的。EventLoopGroup handlerLoopGroup=new EventLoopGrooup();设置线程数量是多核数量的2倍
多路复用IO与NIO的更多相关文章
- 传统IO与NIO的比较
本文并非Java.io或Java.nio的使用手册,也不是如何使用Java.io与Java.nio的技术文档.这里只是尝试比较这两个包,用最简单的方式突出它们的区别和各自的特性.Java.nio提出了 ...
- IO通信模型(三)多路复用IO
多路复用IO 从非阻塞同步IO的介绍中可以发现,为每一个接入创建一个线程在请求很多的情况下不那么适用了,因为这会渐渐耗尽服务器的资源,人们也都意识到了这个 问题,因此终于有人发明了IO多路复用.最大的 ...
- 理解IO、NIO、 AIO
转载:https://baijiahao.baidu.com/s?id=1586112410163034993&wfr=spider&for=pc nio 同步: 自己亲自出马持银行卡 ...
- Java多线程:Linux多路复用,Java NIO与Netty简述
JVM的多路复用器实现原理 Linux 2.5以前:select/poll Linux 2.6以后: epoll Windows: IOCP Free BSD, OS X: kqueue 下面仅讲解L ...
- JAVA I/O(六)多路复用IO
在前边介绍Socket和ServerSocket连接交互的过程中,读写都是阻塞的.套接字写数据时,数据先写入操作系统的缓存中,形成TCP或UDP的负载,作为套接字传输到目标端,当缓存大小不足时,线程会 ...
- Java提供了哪些IO方式?IO, BIO, NIO, AIO是什么?
IO一直是软件开发中的核心部分之一,而随着互联网技术的提高,IO的重要性也越来越重.纵观开发界,能够巧妙运用IO,不但对于公司,而且对于开发人员都非常的重要.Java的IO机制也是一直在不断的完善,以 ...
- netty1---传统IO和NIO的区别
传统IO: package OIO; import java.io.IOException; import java.io.InputStream; import java.net.ServerSoc ...
- 如何解读 Java IO、NIO 中的同步阻塞与同步非阻塞?
原文链接:如何解读 Java IO.NIO 中的同步阻塞与同步非阻塞? 一.前言 最近刚读完一本书:<Netty.Zookeeper.Redis 并发实战>,个人觉得 Netty 部分是写 ...
- java的nio之:java的nio系列教程之java的io和nio的区别
当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异.它们的使用场景,以及它们如何影响您的代 ...
随机推荐
- NOIWC前的交流题目汇总
RT 2018.12.27 i207M:BZOJ 4695 最假女选手 以维护最大值为例,记录最大值和严格次大值和最大值的出现次数,然后取min的时候递归到小于最大值但大于次大值修改,这个就是最重要的 ...
- python之旅:函数对象、函数嵌套、名称空间与作用域、装饰器
一 函数对象 一 函数是第一类对象,即函数可以当作数据传递 #1 可以被引用 #2 可以当作参数传递 #3 返回值可以是函数 #3 可以当作容器类型的元素 二 利用该特性,优雅的取代多分支的if de ...
- python之旅:并发编程之多进程理论部分
一 什么是进程 进程:正在进行的一个过程或者说一个任务.而负责执行任务则是cpu. 举例(单核+多道,实现多个进程的并发执行): egon在一个时间段内有很多任务要做:python备课的任务,写书的任 ...
- QNX下进程间通信
https://blog.csdn.net/dh314552189/article/details/87879016 server.cpp #include <stdlib.h> #inc ...
- C/C++:文本查询(单词查询)
如题: C/C++: Textqurey.h(方便看都在.h里实现了): // // Created by 徐爱东 on 17/7/10. // #ifndef TEXTQUERY_TEXTQUERY ...
- springcloud的分布式配置Config
1.为什么要统一配置管理? 微服务由多个服务构成,多个服务多个配置,则对这些配置需要集中管理.不同环境不同配置,运行期间动态调整,自动刷新. 统一管理微服务的配置:分布式配置管理的一些组件: zook ...
- Qt ------ QPainter 和控件组件的重绘
使用 QPainter 修改 QPaintDevice 的子类,如果 QPaintDevice 的子类也是 QWidget 的子类,比如自定义QWidget子类.QLabel等,需要把 QPainte ...
- P3924 康娜的线段树
P3924 康娜的线段树 题目描述 小林是个程序媛,不可避免地康娜对这种人类的"魔法"产生了浓厚的兴趣,于是小林开始教她OI. 今天康娜学习了一种叫做线段树的神奇魔法,这种魔法可以 ...
- C# list.ForEach用法
list.ForEach(delegate(T model) { ... });
- 安装lsb_release
lsb_release命令用来查看当前系统的发行版信息(prints certain LSB (Linux Standard Base) and Distribution information.). ...