Java IO系列之四:NIO通信模型
分布式rpc框架有很多,比如dubbo,netty,还有很多其他的产品。但他们大部分都是基于nio的,
nio是非阻塞的io,那么它的内部机制是怎么实现的呢。
1.由一个专门的线程处理所有IO事件,并负责分发。
2.事件驱动机制,事件到来的时候触发操作,不需要阻塞的监视事件。
3.线程之前通过wait,notify通信,减少线程切换。


NIO使用步骤

服务端步骤:
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set; public class NIOServer {
public static void main(String[] args) throws IOException {
new Thread(new ReactorTask()).start();
} public static class ReactorTask implements Runnable {
private Selector selector;
public ReactorTask() {
try {
// 第一步:打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道
ServerSocketChannel acceptorSvr = ServerSocketChannel.open(); // 第二步:绑定监听端口,设置连接为非阻塞模式
acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName("localhost"), 1234));
acceptorSvr.configureBlocking(false); // 第三步:创建Reactor线程,创建多路复用器并启动线程
selector = Selector.open(); // 第四步:将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听Accept事件
SelectionKey key = acceptorSvr.register(selector, SelectionKey.OP_ACCEPT); } catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void run() {
// 第五步:在run方法中无限循环体内轮询准备就绪的Key
while (true) {
try {
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
if (key.isValid()) {
// 处理新接入的请求消息
if (key.isAcceptable()) {
// 第六步:多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
// 第七步:设置客户端链路为非阻塞模式
sc.configureBlocking(false);
sc.socket().setReuseAddress(true);
// 第八步:将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,读取客户端发送的网络消息
sc.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
// 第九步:异步读取客户端请求消息到缓存区
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer); // 第十步:对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("The time server receive order : " + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)
? new java.util.Date(System.currentTimeMillis()).toString()
: "BAD ORDER";
//写应答
byte[] bytes2 = currentTime.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes2.length);
writeBuffer.put(bytes2);
writeBuffer.flip();
sc.write(writeBuffer);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
}
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
}
注意:如果发送区TCP缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入TCP缓冲区
客户端步骤:

客户端:
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.Set; public class TimeClientHandle implements Runnable { private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel; private volatile boolean stop; public TimeClientHandle(String host, int port) {
this.host = host == null ? "127.0.0.1" : host;
this.port = port;
try {
//第一步:打开SocketChannel,用于创建客户端连接
socketChannel = SocketChannel.open();
//第二步:设置SocketChannel为非阻塞模式
socketChannel.configureBlocking(false);
//第三步:创建多路复用器(在Reactor线程中)
selector = Selector.open(); } catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
} @Override
public void run() {
try {
// 第四步:socketChannel发起连接
if (socketChannel.connect(new InetSocketAddress(host, port))) {
//第五步:如果直接连接成功,则注册到多路复用器上
socketChannel.register(selector, SelectionKey.OP_READ);
//第六步:发送请求消息,读应答
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
socketChannel.write(writeBuffer);
if (!writeBuffer.hasRemaining())
System.out.println("Send order 2 server succeed.");
} else
socketChannel.register(selector, SelectionKey.OP_CONNECT);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stop) {
try {
//第七步:多路复用器在run的无限循环体内轮询准备就绪的Key
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
if (key.isValid()) {
//第八步:将连接成功的Channel注册到多路复用器上
// 判断是否连接成功
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
//发送请求消息,读应答
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
sc.write(writeBuffer);
if (!writeBuffer.hasRemaining())
System.out.println("Send order 2 server succeed.");
} else
System.exit(1);// 连接失败,进程退出
}
//监听读操作,读取服务端写回的网络信息
if (key.isReadable()) {
//第九步:读取信息到缓冲区
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("Now is : " + body);
this.stop = true;
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
}
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
} // 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
if (selector != null)
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.dxz.springsession.nio.demo6;
public class TimeClient {
/**
* @param args
*/
public static void main(String[] args) { int port = 1234;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new Thread(new TimeClientHandle("127.0.0.1", port), "TimeClient-001")
.start();
}
}
抄录地址:
Java IO系列之四:NIO通信模型的更多相关文章
- java io系列16之 PrintStream(打印输出流)详解
本章介绍PrintStream以及 它与DataOutputStream的区别.我们先对PrintStream有个大致认识,然后再深入学习它的源码,最后通过示例加深对它的了解. 转载请注明出处:htt ...
- Java IO系列之一:IO
1. 概述 Java IO一般包含两个部分: 1.java.io包中堵塞型IO: 2.java.nio包中的非堵塞型IO,通常称为New IO. java.io包下,分为四大块近80个类: 1.基于字 ...
- java io系列21之 InputStreamReader和OutputStreamWriter
InputStreamReader和OutputStreamWriter 是字节流通向字符流的桥梁:它使用指定的 charset 读写字节并将其解码为字符.InputStreamReader 的作用是 ...
- java io系列25之 PrintWriter (字符打印输出流)
更多内容请参考:java io系列01之 "目录" PrintWriter 介绍 PrintWriter 是字符类型的打印输出流,它继承于Writer.PrintStream 用于 ...
- java io系列01之 "目录"
java io 系列目录如下: 01. java io系列01之 "目录" 02. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括 ...
- java io系列06之 序列化总结(Serializable 和 Externalizable)
本章,我们对序列化进行深入的学习和探讨.学习内容,包括序列化的作用.用途.用法,以及对实现序列化的2种方式Serializable和Externalizable的深入研究. 转载请注明出处:http: ...
- java io系列15之 DataOutputStream(数据输出流)的认知、源码和示例
本章介绍DataOutputStream.我们先对DataOutputStream有个大致认识,然后再深入学习它的源码,最后通过示例加深对它的了解. 转载请注明出处:http://www.cnblog ...
- java io系列17之 System.out.println("hello world")原理
我们初学java的第一个程序是"hello world" public class HelloWorld { public static void main(String[] ar ...
- java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)
我们以ByteArrayInputStream,拉开对字节类型的“输入流”的学习序幕.本章,我们会先对ByteArrayInputStream进行介绍,然后深入了解一下它的源码,最后通过示例来掌握它的 ...
随机推荐
- Oracle 时间字段显示不正确,类型错误
一.知识点 给Oracle的date类型字段设置默认值[设置为当前时间] to_date(to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'),'yyyy-mm-dd hh ...
- quartz定时任务cron表达式
cron 表达式格式:秒 分 时 日 月 周 年(可选) 子表达式范围 字段名 字段解释 值范围 特殊字符 Seconds 秒 0~59 , - * / Minutes 分 0~59 , - * / ...
- pipe size设置
我所用的软件架构,使用pipe来实现线程之间的大量数据的传输.在实际操作中,pipe中传输的是数据的指针,而不是数据本身. 但是在调试过程中,我发现,如果我尝试往pipe里面write10000个指 ...
- easyui实现分页
主要参考官方的文档,欢迎评论 1.集成easyui,下面是我的引入方式,我引入到了head.html 每次只要引入该页面就可以了. <!-- easyui样式支持 --><link ...
- ANT与SVN集成
需求描述:从SVN服务器检出最新代码,编译,打包,发布测试环境. 解决方案:使用ANT官网提供的扩展工具中的svnant检出代码. 环境介绍: 操作系统:windows 7 64bit ant版本:a ...
- scp远程拷贝文件及文件夹
scp : 远程copy 命令 -r : 递归copy 从Linux Copy 到 Linux 从Linux Copy 到 Windows (当前目录使用. 就可以了) scp -r root@10. ...
- Bootstrap -- 插件: 提示工具、弹出框、 警告框消息
Bootstrap -- 插件: 提示工具.弹出框. 警告框消息 1. 提示工具(Tooltip)插件:根据需求生成内容和标记. 使用提示工具: <!DOCTYPE html> <h ...
- ASP.NET MVC 下自定义 ModelState 扩展类,响应给 AJAX
ModelStateExtensions.cs using System.Collections.Generic; using System.Linq; using System.Web.Mvc; n ...
- Redis5.0.4复制
redis的复制很简单,由于资源限制,本例中采用两台虚拟机,每台虚拟机安装两个redis实例,共四个来测试 一.安装redis https://www.cnblogs.com/qq931399960/ ...
- C#需要在项目程序生成前后执行相关的事件
分享4: 需求:需要在项目程序生成前后执行相关的事件,比如:需要将某个文件拷贝到bin\Debug中,或者创建某文件夹等. 分析:我们可利用项目属性(选择项目右键,选择属性)中的“生成事件”预定义相关 ...