Java网络编程----NIO编程
一.介绍
有人叫NIO为New IO,有的人把NIO叫做Nonblock IO,这里还是比较倾向于后者,非阻塞IO.NIO有几个重要的基本概念:Buffer(缓冲区),Channel(管道,通道),Selector(选择器,多路复用器).NIO的本质就是避免原始的TCP建立连接使用三次握手的操作,减少连接的开销.模型如下图:
1.1 Buffer
Buffer是一个对象,它包括一些要写入或要读取的数据.在NIO类库中加入Buffer对象,体现了新库与原IO的一个重要区别 .在面向流的IO中,可以将数据直接写入或读取到Stream对象中.在NIO库中,所有数据都是用缓冲区处理的(读写).缓冲区实质上是一个数组,通常它是一个字节数组(ByteBuffer),也可以使用其他类型的数组.这个数组为缓冲区提供了数据的访问读写等操作属性,如位置,容量,上限等概念.
Buffer类型:我们最常用的就是ByteBuffer,实际上每种Java基本类型都对应了一种缓冲区(出了Boolean类型)
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
package com.east.nio;
import java.nio.IntBuffer;
/**
*
* @author
*/
public class TestBuffer {
public static void main(String[] args) {
// TODO Auto-generated method stub
//1.基本操作
//创建指定长度的缓冲区
IntBuffer buf = IntBuffer.allocate(10);
buf.put(13); //往缓冲区放元素
buf.put(21);
buf.put(35);
//把位置复位为0,也就是position位置: 3-->0
buf.flip();
System.out.println("使用filp复位: " + buf);
System.out.println("容量为 : " + buf.capacity()); //容量一旦初始化后不允许改变(Wrap方法包裹数组除外)
System.out.println("限制为 : " + buf.limit()); //由于只装载了三个元素,所以可读取或者操作的元素为3,即limit = 3
System.out.println("获取下标为1的元素:" + buf.get(1));
System.out.println("get(index)方法,position位置不改变:" + buf);
buf.put(1,4);
System.out.println("put(index,change)方法,position位置不变: " + buf);
for(int i=0; i<buf.limit(); i++){
//调用get方法会使其缓冲区位置(position)向后递增一位
System.out.println(buf.get() + "\t");
}
System.out.println("buf对象遍历之后为:" + buf);
//2 wrap方法使用
//wrap方法会包裹一个数组:一般这种用法不会先初始化缓存对象的长度,因为没有意义,最后还会被wrap所包裹的数组覆盖掉
//并且wrap方法修改缓冲区对象的时候,数组本身也会跟着发生变化
int [] arr = new int [] {1,2,5};
IntBuffer buf1 = IntBuffer.wrap(arr);
System.out.println(buf1);
IntBuffer buf2 = IntBuffer.wrap(arr, 0, 2);
//这样使用表示容量为数组arr的长度,但是可操作的元素只有实际进入缓冲区的元素长度
System.out.println(buf2);
//3.其他方法
IntBuffer buf1 = IntBuffer.allocate(10);
int [] arr = new int []{1,2,5};
buf1.put(arr);
System.out.println(buf1);
//一种复制方法
IntBuffer buf3 = buf1.duplicate();
System.out.println(buf3);
//设置buf1的位置属性
//buf1.position(0);
buf1.flip();
System.out.println(buf1);
System.out.println("可读数据为: " + buf1.remaining());
int [] arr2 = new int [buf1.remaining()];
//将缓冲区数据放入到arr2数组中去
buf1.get(arr2);
for(int i : arr2){
System.out.print(Integer.toString(i) + ",");
}
}
}
1.2 Channel
通道(Channel),它就像自来水管道一样,网络数据通过Channel读取和写入,通道与流的不同之处在于通道是双向的,而流只是一个方向上移动(一个流必须是InputStream或者OutputStream的子类),而通道可以用于读,写或者二者同时进行,最关键的是可以与多路复用器结合起来,有多种的状态位,方便多路复用器去识别.事实上通道分为两大类,一类是网络读写(SelectableChannel),一类用于文件操作(FileChannel),我们使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子类
1.3 Selector(一)
多路复用器(Selector),它NIO编程的基础,非常重要.多路复用器提供选择已经就绪的任务的能力.简单地说,就是Selector会不断地轮询注册在其上的通道(Channel),如果某个通道发生了读写操作,这个通道就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以取得就绪的Selector集合,从而进行后续的IO操作.
一个多路复用器(Selector)可以负责成千上万的Channel通道,没有上限.这也是JDK使用了epoll代替了传统的select实现,获得连接句柄没有限制.这也意味着我们只要一个线程负责Selector的轮询,就可以接入成千上万的客户端,这也是JDK NIO库的巨大进步.
1.4 Selector(二)
Selector线程就类似一个管理者(Master),管理了成千上万个管道,然后轮询那个管道的数据已经准备好,通知CPU执行IO的读取和写入操作.
Selector模式:当IO事件(管道)注册到选择器以后,Selector会分配给每个管道一个Key值,相当于标签.Selector选择器是以轮询的方式进行查找注册的所有IO事件(管道),当我们的IO事件(管道)准备就绪后,Select就会识别,会通过Key值来找到相应的管道,进行相关的数据处理操作(从管道里读或写数据,写到我们的数据缓冲区中)
每个管道都会对选择器进行注册不同的事件状态,以便选择器查找,
SelectionKey.OP_CONTENT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
二.NIO编程示例
package com.east.nio.modou;
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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class Server implements Runnable {
//1.多路复用器(管理所有的通道)
private Selector selector;
//2.建立缓冲区
private ByteBuffer readBuf = ByteBuffer.allocate(1024);
//3.
private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
public Server(int port) {
try {
//1.打开多路复用器
this.selector = Selector.open();
//2.打开服务器通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//3.设置服务器通道为非阻塞模式
ssc.configureBlocking(false);
//4.绑定地址
ssc.bind(new InetSocketAddress(port));
//5.把服务器通道注册到多路复用器上,并且监听阻塞事件
ssc.register(this.selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start,Port :" + port);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// TODO Auto-generated constructor stub
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
//1.必须要让多路复用器开始监听
this.selector.select();
//2.返回多路复用器已经选择的结果集
Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
//3.进行遍历
while(keys.hasNext()){
//4.获取一个选择的元素
SelectionKey key = keys.next();
//5.如果有.则直接从容器中移除
keys.remove();
//6.如果是有限的
if(key.isValid()){
//7.如果为阻塞状态
if(key.isAcceptable()){
this.accept(key);
}
//8.如果为可读状态
if(key.isReadable()){
this.read(key);
}
//9.写数据
if(key.isWritable()){
//this.write(key); //ssc
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void accept(SelectionKey key){
try {
//1.获取服务通道
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//2.执行阻塞方法(等待客户端的通道)
SocketChannel sc = ssc.accept();
//3.设置阻塞模式
sc.configureBlocking(false);
//4.注册到多路复用器上,并设置读取标识
sc.register(this.selector, SelectionKey.OP_READ);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void read(SelectionKey key){
try {
//1.清空缓冲区旧的数据
this.readBuf.clear();
//2.获取之前注册的socket通道对象
SocketChannel sc = (SocketChannel) key.channel();
//3.读取数据
int count = sc.read(this.readBuf);
//4.如果没有数据
if(count == -1){
key.channel().close();
key.cancel();
return ;
}
//5.有数据则进行读取,读取之前需要进行复位方法(把poition和limit进行复位)
this.readBuf.flip();
//6.根据缓冲区的数据长度创建相应大小的byte数组,接受缓冲区数据
byte [] bytes = new byte[this.readBuf.remaining()];
//7.接受缓冲区数据
this.readBuf.get(bytes);
//8.打印结果
String body = new String(bytes).trim();
System.out.println("Server :" + body);
//9.可以给客户端写数据...
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new Server(8765)).start();
}
}
package com.east.nio.modou;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* 使用NIO实现一边写数据,一边读数据
* @author lhy
*
*/
public class Client {
//需要一个Selector
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建连接的地址
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765);
//声明连接通道
SocketChannel sc = null;
//建立缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
//打开通道
sc = SocketChannel.open();
//进行连接
sc.connect(address);
while(true){
//定义一个字节数组
byte [] bytes = new byte [1024];
System.in.read(bytes);
/*
* 关键的步骤
*/
//把数据放到缓冲区中
buf.put(bytes);
//对缓冲区进行复位
buf.flip();
//写出数据
sc.write(buf);
//清空缓冲区数据
buf.clear();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(sc != null){
try {
sc.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
还没有评论,来说两句吧...