潍坊Java培训
达内潍坊中心

15265420612

热门课程

JAVA NIO构建I/O多路复用的请求模型

  • 时间:2017-09-30
  • 发布:互联网
  • 来源:互联网

    当前环境

    jdk == 1.8

    代码地址

    git 地址:github.com/jasonGeng88…

    知识点

    nio 下 I/O 阻塞与非阻塞实现

    SocketChannel 介绍

    I/O 多路复用的原理

    事件选择器与 SocketChannel 的关系

    事件监听类型

    字节缓冲 ByteBuffer 数据结构

    场景

    我们使用java.net.socket类来实现了这样的需求,以一线程处理一连接的方式,并配以线程池的控制,貌似得到了当前的最优解.可是这里也存在一个问题,连接处理是同步的,也就是并发数量增大后,大量请求会在队列中等待,或直接异常抛出.

    为解决这问题,我们发现元凶处在"一线程一请求"上,如果一个线程能同时处理多个请求,那么在高并发下性能上会大大改善.这里就借住 JAVA 中的 nio 技术来实现这一模型.

    nio 的阻塞实现

    关于什么是 nio,从字面上理解为 New IO,就是为了弥补原本 I/O 上的不足,而在 JDK 1.4 中引入的一种新的 I/O 实现方式.简单理解,就是它提供了 I/O 的阻塞与非阻塞的两种实现方式(当然,默认实现方式是阻塞的.).

    下面,我们先来看下 nio 以阻塞方式是如何处理的.

    建立连接

    有了上一篇 socket 的经验,我们的第一步一定也是建立 socket 连接.只不过,这里不是采用 new socket() 的方式,而是引入了一个新的概念 SocketChannel.它可以看作是 socket 的一个完善类,除了提供 Socket 的相关功能外,还提供了许多其他特性,如后面要讲到的向选择器注册的功能.

    类图如下:

    潍坊Java培训

    建立连接代码实现:

    // 初始化 socket,建立 socket 与 channel 的绑定关系SocketChannel socketChannel = SocketChannel.open();// 初始化远程连接地址SocketAddress remote = new InetSocketAddress(this.host, port);// I/O 处理设置阻塞,这也是默认的方式,可不设置socketChannel.configureBlocking(true);// 建立连接socketChannel.connect(remote);

    获取 socket 连接

    因为是同样是 I/O 阻塞的实现,所以后面的关于 socket 输入输出流的处理,和上一篇的基本相同.唯一差别是,这里需要通过 channel 来获取 socket 连接.

    获取 socket 连接

    Socket socket = socketChannel.socket();

    处理输入输出流

    PrintWriter pw = getWriter(socketChannel.socket());BufferedReader br = getReader(socketChannel.socket());

    完整示例

    package com.jason.network.mode.nio;import com.jason.network.constant.HttpConstant;import com.jason.network.util.HttpUtil;import java.io.*;import java.net.InetSocketAddress;import java.net.Socket;import java.net.SocketAddress;import java.nio.channels.SocketChannel;public class NioBlockingHttpClient { private SocketChannel socketChannel; private String host; public static void main(String[] args) throws IOException { for (String host: HttpConstant.HOSTS) { NioBlockingHttpClient client = new NioBlockingHttpClient(host, HttpConstant.PORT); client.request(); } } public NioBlockingHttpClient(String host, int port) throws IOException { this.host = host; socketChannel = SocketChannel.open(); socketChannel.socket().setSoTimeout(5000); SocketAddress remote = new InetSocketAddress(this.host, port); this.socketChannel.connect(remote); } public void request() throws IOException { PrintWriter pw = getWriter(socketChannel.socket()); BufferedReader br = getReader(socketChannel.socket()); pw.write(HttpUtil.compositeRequest(host)); pw.flush(); String msg; while ((msg = br.readLine()) != null){ System.out.println(msg); } } private PrintWriter getWriter(Socket socket) throws IOException { OutputStream out = socket.getOutputStream(); return new PrintWriter(out); } private BufferedReader getReader(Socket socket) throws IOException { InputStream in = socket.getInputStream(); return new BufferedReader(new InputStreamReader(in)); }}

    nio 的非阻塞实现

    原理分析

    nio 的阻塞实现,基本与使用原生的 socket 类似,没有什么特别大的差别.

    下面我们来看看它真正强大的地方.到目前为止,我们将的都是阻塞 I/O.何为阻塞 I/O,看下图:

    潍坊Java培训

    我们主要观察图中的前三种 I/O 模型,关于异步 I/O,一般需要依靠操作系统的支持,这里不讨论.

    从图中可以发现,阻塞过程主要发生在两个阶段上:

    第一阶段:等待数据就绪;

    第二阶段:将已就绪的数据从内核缓冲区拷贝到用户空间;

    这里产生了一个从内核到用户空间的拷贝,主要是为了系统的性能优化考虑.假设,从网卡读到的数据直接返回给用户空间,那势必会造成频繁的系统中断,因为从网卡读到的数据不一定是完整的,可能断断续续的过来.通过内核缓冲区作为缓冲,等待缓冲区有足够的数据,或者读取完结后,进行一次的系统中断,将数据返回给用户,这样就能避免频繁的中断产生.

    了解了 I/O 阻塞的两个阶段,下面我们进入正题.看看一个线程是如何实现同时处理多个 I/O 调用的.从上图中的非阻塞 I/O 可以看出,仅仅只有第二阶段需要阻塞,第一阶段的数据等待过程,我们是不需要关心的.不过该模型是频繁地去检查是否就绪,造成了 CPU 无效的处理,反而效果不好.如果有一种类似的好莱坞原则- "不要给我们打电话,我们会打给你" .这样一个线程可以同时发起多个 I/O 调用,并且不需要同步等待数据就绪.在数据就绪完成的时候,会以事件的机制,来通知我们.这样不就实现了单线程同时处理多个 IO 调用的问题了吗?即所说的"I/O 多路复用模型".

更多潍坊Java培训相关资讯,请扫描下方二维码

潍坊Java培训

上一篇:Java兰姆达(Lambda)表达式 和方法引用实战
下一篇:简明编程系列之Java的算术操作符

简明编程系列之Java的算术操作符

JAVA NIO构建I/O多路复用的请求模型

Java兰姆达(Lambda)表达式 和方法引用实战

java学习路线图

选择城市和中心
贵州省

广西省

海南省