NIO

简述:

NIO是在jdk1.4之后加入的一种基于缓冲区(buffer)和通道(channel)的I/O方式,

nio是同步非阻塞的i/o模式,同步是指线程不断地轮询i/o事件,非阻塞是在处理i/o事件的同时,还可以去处理其它的事情。

同步的核心是Selector(选择器),代替的线程本身的轮询i/o事件,避免了阻塞同时线程的不必要消耗,

非阻塞的核心就是通道和缓冲区,当有i/o事件就绪时,写入到缓冲区,保证i/o成功。而无需等待。

为什么使用nio?

使用nio是为了java程序员可以实现高速的i/o操作,不用编写自定义的本机代码。nio将最耗时的i/o操作转回到操作系统,因而提交了效率。

NIO 通道 缓冲区:

通道可以被异步读写,通道始终读写缓冲区。

channel ——> buffer

buffer ——> channel

数据可以从通道读取到缓冲区,也可以是冲缓冲区读取到通道。

Channel有很多种实现

FileChannel  从文件中读取数据

DataGramChannel 通过udp连接在网络中读取数据

SocketChannel 能通过socket连接在网络中读取数据

ServerSocketChannel 可以监听新进来的tcp连接

Buffer 的实现:

byteBuffer  charBuffer  longBuffer DoubleBuffer  ...等

Selector 选择器:

selector 允许单个选择器检测多个通道。


使用selector需要向它注册多个channel,然后调用他的select() 方法,这个方法会一直阻塞到某个通道有事件发生。一旦有返回值,线程就可以处理了。

selector 的创建:

Selector selector = Selector.open() //获取一个选择器

向Selector注册channel:

要想selector 与channel 配合使用,就必须吧channel注册到selector上,并使Channel处于非阻塞状态,这意味著FileChanne不能切换到非阻塞模式,而套接字可以;

Channel.configureBlocking(fasle);// 设置为非阻塞模式

SelectionKey seletctorKey = channel.register(selector,SelectionKey.OP_READ);

SelectionKey 的监听事件有OP_READ     OP_WRITE     OP_ACCEPT    OP_CONNECT

通道触发了一个事件就表示该事件变成就绪状态,所以某个channel成功连接到一个服务器称为‘连接就绪’。

一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。

附加对象:

可以将一个对象获更多的信息放到selector上,这样就可以更方便的获取channel信息。使用方法如下:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

还可以在注册通道时增加附加对象:

SelectionKey selectkey = SelectionKey.register(channel,SelectionKey.OP_READ,attachedObj);

通过selector选择通道:

select()方法会返回读事件已经就绪的那些通道。

int select() 
   int select(long timeout)
   int selectNow()

select()阻塞到至少有一个通道在你注册的事件上就绪了。

select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。

selectNow()不会阻塞,不管什么通道就绪都立刻返回。

select()方法返回的int值表示有多少通道已经就绪。

selectedKeys()

一旦调用了select()方法,并且返回值表明有一个或则多个通道处于就绪状态就可以使用selector 的SelectedKeys()方法获取就绪状态的通道。

Set selectedKeys = selector.selectedKeys();

然后可以遍历 集合得到单个的就绪通道:

Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {// 连接被接受
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) { // 是否连接
// a connection was established with a remote server.
} else if (key.isReadable()) { //可读
// a channel is ready for reading
} else if (key.isWritable()) { //可写
// a channel is ready for writing
}
keyIterator.remove();
}

注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。

close()
用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。

 Buffer(缓冲区):

缓冲区本质上是一个既可以写入数据也可以读取数据的内存,这部分被包装成了Buffer对象,并提供了一系列的方法使用,访问这部分的空间。

buffer使用一般是以下几个步骤:

ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 创建缓冲区

写入数据到缓冲区Buffer

使用flip()切换缓冲区状态

读取缓冲区buffer

关闭缓冲区clear清空

这里顺便插入下:

随机流(RandomAccessFile)不属于IO流,支持对文件的读取和写入随机访问。

对象声明:RandomAccessFile raf = newRandomAccessFile(File file, String mode);

其中参数 mode 的值可选 "r":可读,"w" :可写,"rw":可读性;

下面小例子:

        String filePath = "s://io.txt";

        File file = new File(filePath);

        RandomAccessFile raf = new RandomAccessFile(file, "rw");

        FileChannel fc = raf.getChannel(); // 获取文件通道

        ByteBuffer bb = ByteBuffer.allocate(1024);// 创建缓冲区

        byte[] buf = new byte[bb.remaining()];
int len;
// 从通道读取一个数据到缓冲区
while ((len = fc.read(bb)) != -1) {
bb.flip();// flip方法将Buffer从写模式切换到读模式
bb.get(buf, 0, len); // 将缓冲区数据读到byte[]中 }
System.out.println(new String(buf, Charset.forName("GBK")));
bb.clear();

 

Buffer的capacity,position和limit3个属性:

capacity 不论buffer是在读或者写状态,capacity的含义都不会改变。

模式说明:

position:当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.

limit:表示允许写入的数据最大位置。在read模式下表示读取数据的最大限度。

capacity:作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往

 Buffer的分配

要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有一个allocate方法。下面是一个分配48字节capacity的ByteBuffer的例子。

ByteBuffer buf = ByteBuffer.allocate(48);

这是分配一个可存储1024个字符的CharBuffer:

CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中写数据

写数据到Buffer有两种方式:

从Channel写到Buffer。

通过Buffer的put()方法写到Buffer里。

从Channel写到Buffer的例子

int bytesRead = inChannel.read(buf);

通过put方法写Buffer的例子:

buf.put(127);

put方法有很多版本,允许你以不同的方式把数据写入到Buffer中。

flip()方法

flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。

换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。

从Buffer中读取数据
从Buffer中读取数据有两种方式:

从Buffer读取数据到Channel。
使用get()方法从Buffer中读取数据。
从Buffer读取数据到Channel的例子:

int bytesWritten = inChannel.write(buf);
使用get()方法从Buffer中读取数据的例子

byte aByte = buf.get();
get方法有很多版本,允许你以不同的方式从Buffer中读取数据。

clear()方法
一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。

在Java NIO中,

如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel传输到另外一个channel。

transferFrom()

FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中(译者注:这个方法在JDK文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。下面是一个简单的例子:

        String filePath = "s://io.txt";
String filePath2 = "s://111.txt"; File file = new File(filePath);
File file2 = new File(filePath2); RandomAccessFile raf = new RandomAccessFile(file, "rw");
RandomAccessFile raf2 = new RandomAccessFile(file2, "rw"); FileChannel fc = raf.getChannel();
FileChannel fc2 = raf2.getChannel();
long count = fc.size();
int position = 0;
fc2.transferFrom(fc, position, count);// fc2 目标文件 fc 被复制的文件
//fc.transferTo(position, count, fc2);

线面来个稍完整点nio通信的例子:

NioServer:

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
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.Scanner;
import java.util.Set; public class NIOServer { private ServerSocketChannel serverSocketChannel;
public String IP = "127.0.0.1"; private Selector selector; static int port = 8989; public NIOServer() {
try {
selector = Selector.open(); serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(IP, port)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务器已启动,端口号:" + port); } catch (IOException e) {
e.printStackTrace();
}
} void receiceMessage() {
while (true) {
try {
selector.select();
Set<SelectionKey> selectors = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectors.iterator(); while (iterator.hasNext()) {
SelectionKey key = iterator.next();
try {
heandle(key);
} catch (IOException e) {
e.printStackTrace();
}
iterator.remove();
} } catch (Exception e) {
e.printStackTrace();
}
}
} void heandle(SelectionKey selectionKey) throws IOException { if (selectionKey.isValid() && selectionKey.isAcceptable()) { // 有效的連接 ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
// 通过ServerSocketChannel的accept创建SocketChannel实例
// 完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
SocketChannel scoketCl = serverSocketChannel.accept();
// 设置飞阻塞模式
scoketCl.configureBlocking(false); scoketCl.register(selector, SelectionKey.OP_READ); // 注册读 事件 } else if (selectionKey.isValid() && selectionKey.isReadable()) {// 是否可读
SocketChannel sc = (SocketChannel) selectionKey.channel(); String handelResult = null;
// 创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 通道 read into byteBuffer
int byteCounts = sc.read(byteBuffer); if (byteCounts > 0) {
// 接受数据 处理数据 返回数据
byteBuffer.flip(); // 转换为刻度 模式 byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); String result = new String(bytes, "UTF-8"); System.out.println("服务端接收的数据:" + result);
// 处理
handelResult = "处理后的数据是server" + result; System.out.println("处理后的数据是:" + handelResult); serverSendMessage(sc, handelResult);
} else if (byteCounts < 0) {
selectionKey.cancel();
sc.close();
} } } static void serverSendMessage(SocketChannel socketChannel, String message) throws IOException {
System.out.println("message" + message);
byte[] bytes = message.getBytes(); ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); byteBuffer.put(bytes);// 添加数据到缓冲区 byteBuffer.flip();// 模式转换 socketChannel.write(byteBuffer); // 写入数据到socket通道
} public static void main(String[] args) {
NIOServer server = new NIOServer();
server.receiceMessage();
} }

NioClient

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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; /**
* channel 注册到selector 上 必须制定关注的事件类型。具体的事件承载于Selectored上。
*/ public class NIOClient { public static String IP = "127.0.0.1";
static Selector selector;
final static int port = 8989;
static Charset charset = Charset.forName("UTF-8");
private static SocketChannel socketChannel; NIOClient() throws IOException {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(IP, port));
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} private static void receiveRead(SelectionKey selectiontKey) throws IOException {
try {
if (selectiontKey.isValid() && selectiontKey.isReadable()) { SocketChannel socket = (SocketChannel) selectiontKey.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int readByte = socket.read(byteBuffer);// 返回独到的字节数
System.out.println(readByte + "****");
if (readByte > 0) {// 读取数据 byteBuffer.flip(); byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes);// 将缓冲区数据读到byte[]中 String result = new String(bytes, "UTF-8");
System.out.println("客户端接收到的数据:" + result);
byteBuffer.clear();
} else if (readByte < 0) {
selectiontKey.cancel();
socketChannel.close();
}
selectiontKey.interestOps(SelectionKey.OP_READ);
}
} catch (Exception e) {
System.out.println("服务端连接已关闭");
}
} static void sendm(String message) throws IOException {
socketChannel.write(charset.encode(message));
} public static void main(String[] args) throws IOException { NIOClient.Th th = new NIOClient().new Th();
th.start(); Scanner scan = new Scanner(System.in);// 这里向服务端发送数据,同时启动了一个键盘监听器
while (scan.hasNextLine()) {
System.out.println("输入数据:\n");
// 读取键盘的输入
String line = scan.nextLine();
// 将键盘的内容输 到SocketChanenel中
sendm(line);
}
scan.close();
} private class Th extends Thread { @Override
public void run() {
try {
while (selector.select() > 0) {
Set<SelectionKey> set = selector.selectedKeys();
Iterator<SelectionKey> iter = set.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
try {
receiveRead(key);
} catch (IOException e) {
e.printStackTrace();
}
iter.remove(); }
}
} catch (IOException e) {
e.printStackTrace();
}
} }
}

  

NIO 概述 与 通信实例的更多相关文章

  1. 基于NIO的Socket通信

    一.NIO模式的基本原理: 服务端: 首先,服务端打开一个通道(ServerSocketChannel),并向通道中注册一个通道调度器(Selector):然后向通道调度器注册感兴趣的事件Select ...

  2. 【Java TCP/IP Socket】基于NIO的TCP通信(含代码)

    NIO主要原理及使用 NIO采取通道(Channel)和缓冲区(Buffer)来传输和保存数据,它是非阻塞式的I/O,即在等待连接.读写数据(这些都是在一线程以客户端的程序中会阻塞线程的操作)的时候, ...

  3. Java NIO:NIO概述

    Java NIO:NIO概述 在上一篇博文中讲述了几种IO模型,现在我们开始进入Java NIO编程主题.NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题.本文下面分别从Java ...

  4. Flex通信-与Java实现Socket通信实例

    Flex通信-与Java实现Socket通信实例  转自:http://blessht.iteye.com/blog/1136888 博客分类: Flex 环境准备 [服务器端] JDK1.6,“ja ...

  5. (转载)Java NIO:NIO概述(一)

    Java NIO:NIO概述 在上一篇博文中讲述了几种IO模型,现在我们开始进入Java NIO编程主题.NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题.本文下面分别从Java ...

  6. Linux下简单的socket通信实例

    Linux下简单的socket通信实例 If you spend too much time thinking about a thing, you’ll never get it done. —Br ...

  7. Java NIO学习笔记一 Java NIO概述

    Java NIO概述 Java NIO(新的IO)是Java的替代IO API(来自Java 1.4),这意味着替代标准的 java IO和java Networking API.Java NIO提供 ...

  8. 【安富莱TCPnet网络教程】HTTP通信实例

    第41章      HTTP超文本传输协议基础知识 本章节为大家讲解HTTP(HyperText Transfer Protocol,超文本传输协议),从本章节开始,正式进入嵌入式Web的设计和学习. ...

  9. Java NIO系列教程(一) Java NIO 概述

    <I/O模型之四:Java 浅析I/O模型> 一.阻塞IO与非阻塞IO 阻塞IO: 通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至数 ...

随机推荐

  1. MIUI6系统如何启用root权限的教程

    MIUI6系统有没有办法启用了root权限?大家都清楚,Android机器有root权限,如果手机启用了root相关权限,就能够实现更好的功能,举例子,大家单位的营销部门同事,使用某些营销软件都需要在 ...

  2. Postman应用笔记

    Postman应用: 项目组织格式 Collections 集合--项目--根路径文件夹 文件夹 集合下只支持1级文件夹 文件夹 Request --请求 url 认证参数,头信息,体信息(Autho ...

  3. java中全角半角字符的相互转换的代码

    如下内容是关于java中全角半角字符的相互转换的内容.package com.whatycms.common.util; import org.apache.commons.lang.StringUt ...

  4. Django细节小记

    前记:Django的ORM.模块有很多函数细节,要学会多看文档学习函数的细节 聚合annotate()和aggregate()的使用 简言之,annotate()得到的是查询集,类似all(),只不过 ...

  5. maven install报错 Failed to execute goal on project my-manager-mapper: Could not resolve dependencies for project com.my:my-manager-mapper:jar:0.0.1-SNAPSHOT:

    报错信息为: [ERROR] Failed to execute goal on project my-manager-mapper: Could not resolve dependencies f ...

  6. 登录获取token,token参数关联至所有请求的请求体内

    问题描述: 有些系统接口判断用户是否登录,是校验登录接口成功后传的token值,也就是请求系统所有接口时,前端传参必带登录成功后接口返回的token,后台以此检验是否过期或是否有登录.所有接口都依赖登 ...

  7. 新手vue构建单页面应用实例

    本人写的小程序,功能还在完善中,欢迎扫一扫提出宝贵意见! 步骤: 1.使用vue-cli创建项目2.使用vue-router实现单页路由3.用vuex管理我们的数据流4.使用vue-resource请 ...

  8. django模型系统(一)

    django模型系统(一) djangode ORM ORM:对像关系映射 用python概念去表达数据库 数据库配置(mysql) 安装pumysql 修改项目目录下的__init__.py imp ...

  9. phpquerylist 抓取数据详解

    参考文档 https://doc.querylist.cc/site/index/doc

  10. mysql 循环、游标

    mysql 循环只能在存储过程.代码记录 CREATE DEFINER=`front`@`%` PROCEDURE `a_1`() BEGIN -- 声明变量,接收游标循环变量 DECLARE _co ...