1. NIO, 也可以叫做new I/o。
与之前的i/o对比,最大的特性应该就是它的非阻塞I/O,它也提供阻塞的模式和非阻塞模式,一般使用NIO来编写,都是用于非阻塞的场景(高负载高并发)。
2.缓存区buffer
NIO新类库中加入Buffer对象,可以直接写入或者把数据读到Stram对象中。任何时候访问NIO的数据,都是通过缓存区进行操作。
常见的缓存区是ByteBuffer,一个ByteBuffer提供一组用于操作byte数组,缓存区都是提供了对数据的结构化访问和维护读写位置等信息。
java每个基本类型都有对应:(ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer).
3.通道channel
流是只有一个方向进行移动,通过inputstream和outputstream的子类,朝一个方向移动。
通道是双向的,可以读,写还可以两者同时进行。
channel有用于网络读写的SelectableChannel和用于文件操作的FileChannel。
4.多路复用器Selector
作用:建立一个多路复用器,轮询注册在它身上的Channel,如果有读写的事件发生,Channel就处于工作状态,会被Selector选择出来,通过SelectorKey可以获取Channel通道的集合,进行后续的I/O操作。
一个线程可以负责Selector的轮询,就可以接入所有客户端的请求。
NIO服务端的工作步骤:
①:打开ServerSocketChannel,监听客户端的连接。
②:绑定监听端口,severSocketChannel.socket().bind(new InetSocketAddress(port),1024);
③:创建多路复用器Selector,启动线程、
④:将ServerSocketChannel 注册到Selector上。 Selector轮询这个服务端通道。
⑤:处理客户端接入的事件。ServerSocketChannel ssc=(ServerSocketChannel) key.channel();
SocketChannel sc=ssc.accept();//建立socket通道去接入客户端请求。
⑥:设置客户端连接的socket参数。包括非阻塞,以及将它注册到SelectorKey上去监听。
⑦:异步 读数据到ByteBuffer ,写ByteBuffer数据到SocketChannel通道上去。
NIO客户端工作步骤:
①:打开socketchannel。
②:设置通道参数,TCP参数,非阻塞模式等等。
③:连接服务端。
④:连接失败,socketchannel注册Reactor线程的selector的op_connect事件。
⑤:创建多路复用器selector,启动线程,轮询key,处理连接成功
⑥:成功连接,注册Selector,OP_READ。读取消息到bytebuffer
⑦:decode请求信息
⑧:写bytechannel到socketchannel。
服务端代码:
/**
* @chenxiaowu
*2018年2月7日
*TODO
*/
public class TimeServer {
public static void main(String[] args) {
int port=8080;
//创建一个多路复用类
MultiplexerTimeServer multiplexerTimeServer=new MultiplexerTimeServer(port);
//独立的线程,建立轮询多路复用器 selector,可以处理多个客户端并发接入
new Thread(multiplexerTimeServer,"NIO-SERVER-001").start();
}
}
/**
* @chenxiaowu
*2018年2月7日
*TODO
*通道channel SelectableChannel 网络读写 serverSocketChannel 和socketchannel是它的子类
* Filechannel 文件操作
*多路复用器selector 不断轮询注册在它上面的channel,channel有读写事件,便处于就绪状态,会被selector轮询出来,
* 然后通过selectionkey 获取就绪channel集合,进行后续的i/o操作。
*/
public class MultiplexerTimeServer implements Runnable{
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private boolean stop;
// 初始化多路复用器,绑定监听端口
public MultiplexerTimeServer(int port)
{
try {
//第三步:创建多路复用器,打开。
selector=Selector.open();
// 第一步:打开监听客户端连接,serverSocketChannel
serverSocketChannel=ServerSocketChannel.open();
//第二步:设置连接未非阻塞模式。 绑定监听接口
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port),1024);
//第四步:server通道注册到多路复用器selector上,监听accept事件。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("The time server start in port:"+port);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void stop()
{
this.stop=true;
}
private void handleinput(SelectionKey key) throws IOException
{
if(key.isValid())
{
if(key.isAcceptable())
{
ServerSocketChannel ssc=(ServerSocketChannel) key.channel();
//第六步:检测新的客户端接入。处理新的接入请求,完成tcp三次握手,建立物理链路。
SocketChannel sc=ssc.accept();
//设置客户端为非阻塞模式
sc.configureBlocking(false);
//把新接入的客户端连接注册到多路复用器上,监听读操作,读取客户端发送的网络信息。
sc.register(selector, SelectionKey.OP_ACCEPT);
}
if(key.isReadable())
{
SocketChannel sc=(SocketChannel) key.channel();
java.nio.ByteBuffer readBuffer=java.nio.ByteBuffer.allocate(1024);
//读取客户端请求信息到缓冲区
int readbyte=sc.read(readBuffer);
if(readbyte>0)
{
//
readBuffer.flip();
//readBuffer.remaining() 返回bytebuffer的剩余长度。此长度为实际读取的数组。
byte[] bytes=new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body=new String(bytes, "UTF-8");
System.out.println("The time server accept order:"+body);
String currentTime="QUERY TIME".equalsIgnoreCase(body)?new java.util.Date(System.currentTimeMillis()).toString():"BAD ORDER";
//
dowrite(sc, currentTime);
}else if(readbyte<0)
{
key.cancel();
sc.close();
}
else{
;
}
}
}
}
private void dowrite(SocketChannel channel,String response) throws IOException
{
if(response!=null && response.trim().length()>0)
{
byte[] bytes=response.getBytes();
ByteBuffer writeBuffer=ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
public void run() {
// TODO Auto-generated method stub
while(!stop)
{
try{
//多路复用器轮询时间
selector.select(1000);
//第五步:循环找出准备就绪的key
Set<SelectionKey> selectionKeys=selector.selectedKeys();
Iterator<SelectionKey> it=selectionKeys.iterator();
SelectionKey key=null;
while (it.hasNext()) {
key=it.next();
it.remove();
try{
//添加处理接收信息和返回的信息
handleinput(key);
}catch(Exception e)
{
}
}
}catch(Exception e)
{
}
}
}
}
服务端一个启动类和一个处理类,启动多路复用器,去轮询进行I/O操作。
客户端:
/**
* @chenxiaowu
*2018年2月16日
*TODO
*/
public class TimeClient {
public static void main(String[] args) {
new Thread(new TimeClientHandle("127.0.0.1", 8080),"TimeClient-001").start();
}
}
package testnio.chenio.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* @chenxiaowu
*2018年2月16日
*TODO
*/
public class TimeClientHandle implements Runnable{
private Selector selector;
private SocketChannel socketChannel;
private String host;
private int port;
//判断客户端是否能直接连接上。
private boolean stop;
public TimeClientHandle(String host,int prot)
{
try{
//初始化打开多路复用器。打开socket通道。
selector=Selector.open();
socketChannel=SocketChannel.open();
socketChannel.configureBlocking(false);
}catch(IOException e)
{
e.printStackTrace();
}
}
public void run() {
// TODO Auto-generated method stub
try{
//进入该方法,有两个不同的状态处理。一个是直接连接上,一个重新建立连接
doconnect();
while(!stop)
{
try{
//轮询进行处理
selector.select(1000);
Set<SelectionKey> selectionkey=selector.selectedKeys();
Iterator<SelectionKey> it=selectionkey.iterator();
SelectionKey key=null;
while(it.hasNext())
{
key=it.next();
it.remove();
try{
//处理输入的信息。发送出去的方法。
handleInput(key);
}catch(Exception e)
{
if(key!=null)
{
key.cancel();
if(key.channel()!=null)
{
key.channel().close();
}
}
}
}
}catch(Exception e)
{
e.printStackTrace();
}
}
}catch(Exception e)
{
e.printStackTrace();
System.exit(1);
}
}
private void doconnect()
{
try {
if(socketChannel.connect(new InetSocketAddress(host, port)))
{
//连接上,就注册到selector上,OP_READ读操作
socketChannel.register(selector, SelectionKey.OP_READ);
//进入write。写入buffer缓存区
doWrite(socketChannel);
}else{
//连接不上,就注册,进行connect操作。
socketChannel.register(selector,SelectionKey.OP_CONNECT);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void doWrite(SocketChannel sc) throws IOException
{
byte[] req="QUERY TIME".getBytes();
ByteBuffer writebuffer=ByteBuffer.allocate(req.length);
writebuffer.put(writebuffer);
writebuffer.flip();
sc.write(writebuffer);
if(!writebuffer.hasRemaining())
{
System.out.println("send message success");
}
}
private void handleInput(SelectionKey key) throws ClosedChannelException, IOException
{
if(key.isValid())
{
SocketChannel sc=(SocketChannel) key.channel();
if(key.isConnectable())
{
if(sc.finishConnect())
{
sc.register(selector, SelectionKey.OP_READ);
doWrite(sc);
}else{
System.out.println("连接失败");
}
}
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();
}
}
}
}
}
客户端一个启动类和一个处理。连接上的直接处理读取,连接不上的,重新进行OP_CONNECT事件。
共勉。继续学习。接触AIO之后,进行netty的初始搭建。接触不同开发流程的通讯。