nio编

       NIO,有人称为New IO这是相对于之前传统io的叫法。但是与之前传统的相比,New IO类库的目标是让java支持非阻塞IO,因此是指非阻塞IO。

     NIO类库的简介

     1、缓冲区buffer

     在nio库中,所有的数据都是用缓冲去咧,在读取数据的时候,它是直接读到缓冲区中的;在写入数据的时候,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作的。
      最常用的缓冲区是ByteBuffer,一个ByteBuffer提供了一组能用于操作byte数组。除了ByteBuffer,还有其他的一些缓冲区。缓冲区类图继承图如下:

    2、 通道Channel

  Channel是一个通道,它就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流只是在一个方向上移动(一个流必须是InputStream或是OutputStream子类),而通道可以用于读、写或者两者同时进行。

   实际上Channle可以分为2大类:用于网络读写的SelectableChannel和用于文件操作的FileChannel。

   3、多路复用器Selector

    多路复用器Seletor,它是java nio的编程基础,熟练掌握Seletor对于NIO编程至关重要。多路复用器提供选择已经就绪的任务能力。简单的来讲Selector会不断地轮训注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel集合,进行后续的IO操作。

     一个多路复用器Selector可以同时轮询多个Channel,由于JDk使用了epoll()代替传统的的select实现,所以它没有最大连接句柄1024/2048的限制。这也就意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。

   下面给出nio编程的序列图和对应的源码。

     


步骤一:打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道:

  //打开serverSocketChannel用于监听客户端的连接
      serverSocketChannel = ServerSocketChannel.open();

步骤二:绑定监听端口,设置连接为非阻塞模式,如下:

serverSocketChannel.configureBlocking(false);
      serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);

  步骤三:创建Selector线程:

      selector = Selector.open();

步骤四:将ServerSocketChannel注册到Selector的多路复用器上,监听Accept事件。

  //注册连接事件类型
      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

步骤五:多路复用器在run线程方法的无限循环体内轮询准备就绪的Key,示例代码如下:

Set<SelectionKey> selectionKeys = selector.selectedKeys();
        Iterator<SelectionKey> it = selectionKeys.iterator();
        SelectionKey key = null;
        while (it.hasNext()) {
           .......

          // do with io event
        }

  步骤六:多路复用器监听到有新的客户端接入,处理新接入的请求,完成tcp的三次握手,建立物理连接,示例代码如下:

 //处理新接入的请求信息
      if (key.isAcceptable()) {
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        SocketChannel sc = ssc.accept();
        sc.configureBlocking(false);
        //注册读取事件
        sc.register(selector, SelectionKey.OP_READ);
      }

   上面设置了客户端链路为非阻塞模式,并且将新的客户度的连接注册到多路复用器上,监听读操作,读取客户端发送的网络消息。

 步骤七:异步读取客户端请求消息到缓冲区,如下:

SocketChannel sc = (SocketChannel) key.channel();
        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
        int readBytes = sc.read(readBuffer);

 步骤八:进行消息的编解码,如遇到半包的消息指针reset,继续读取后续的报文。

TimeServer代码如下:



public class TimeSever {


  public static void main(String[] args) throws IOException {
    int port = 8080;
    if (args != null && args.length > 0) {
      try {
        port = Integer.valueOf(args[0]);
      } catch (NumberFormatException e) {


      }
    }

  //多路复用类,用来轮询多路复用器selector
    MultiplexerTimerServer timerServer = new MultiplexerTimerServer(port);
    new Thread(timerServer, "NIO-MultiplexerTimeServer-001").start();
  }

}

有关MultplexerTimeServer的代码如下:

package nio.zou;

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.Date;
import java.util.Iterator;
import java.util.Set;

public class MultiplexerTimerServer implements Runnable {
	private Selector selector;
	private ServerSocketChannel serverSocketChannel;
	private volatile boolean stop;
	public MultiplexerTimerServer(int port) {
		//创建selector线程
		try {
			selector = Selector.open();
			//打开serverSocketChannel用于监听客户端的连接
			serverSocketChannel = ServerSocketChannel.open();
			//设置非阻塞
			serverSocketChannel.configureBlocking(false);
			serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
			//注册连接事件类型
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("The time server is start in port :" + port);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}

	}
	public void stop() {
		this.stop = true;
	}
	@Override
	public void run() {
		while (!stop) {
			try {
				selector.select(1000);
				//获取就绪事件
				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) {
						if (key != null) {
							key.cancel();
							if (key.channel() != null) {
								key.channel().close();
							}
						}
					}

				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		if (selector != null) {
			try {
				selector.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	private void handleInput(SelectionKey key) throws IOException {
		if (key.isValid()) {
			//处理新接入的请求信息
			if (key.isAcceptable()) {
				ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
				SocketChannel sc = ssc.accept();
				sc.configureBlocking(false);
				//注册读取事件
				sc.register(selector, SelectionKey.OP_READ);
			}
			if (key.isReadable()) {
				//read data
				SocketChannel sc = (SocketChannel) key.channel();
				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("the time sever recevie order:" + body);
					String currentTime = "QUERY TIME ORDER".equals(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
					doWrite(sc, currentTime);
				} else if (readBytes < 0) {
					//对端链路进行关闭
					key.cancel();
					sc.close();
				} else
					//读到0字节进行忽略
					;

			}
		}
	}

	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);
		}
	}

}

 需要注意的是ServerSocketChannel设置为异步非阻塞模式,它的backlog设置为1024(最大的连接数)。

 线程的run方法是在while循环体中遍历selector,它的休眠时间为1s。无论是否有读写等事件发生,selector每隔1s被唤醒一次。通过对就绪的Channel集合进行迭代,可以进行网络的异步读写操作。这里作为入门的例子,没有考虑tcp的写半包和读半包问题。

  NIO客户端的时序图如下:


   

 步骤一:打开SocketChannel,绑定客户端地址,如下:

socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
设置为非阻塞。

步骤二:异步连接服务端,并向selector注册事件:

private void doConnect() throws IOException {
		if (socketChannel.connect(new InetSocketAddress(host, port))) {
			socketChannel.register(selector, SelectionKey.OP_READ);
			doWrite(socketChannel);
		} else {
			socketChannel.register(selector, SelectionKey.OP_CONNECT);
		}
	}

步骤三:在多路复用器进行轮训就绪的key,代码如下:

try {
				selector.select(1000);
				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) {
						if (key != null) {
							key.cancel();
							if (key.channel() != null) {
								key.channel().close();
							}
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				System.exit(1);
			}


其代码如下:

public class TimeClient {

	public static void main(String[] args) {
		int port = 8080;
		if (args != null && args.length > 0) {
			try {
				port = Integer.valueOf(args[0]);
			} catch (Exception e) {

			}
		}
		new Thread(new TimeClientHandle("127.0.0.1", port), "TimeClient").start();
	}
}

package nio.zou;

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 {
			selector = Selector.open();
			socketChannel = SocketChannel.open();
			socketChannel.configureBlocking(false);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	@Override
	public void run() {
		try {
			doConnect();
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
		while (!stop) {
			try {
				selector.select(1000);
				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) {
						if (key != null) {
							key.cancel();
							if (key.channel() != null) {
								key.channel().close();
							}
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				System.exit(1);
			}
		}
	}

	public void doWrite(SocketChannel sc) throws IOException {
		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");
		}
	}

	private void handleInput(SelectionKey key) throws 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.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 {
					;//忽略
				}
			}

		}
	}

	private void doConnect() throws IOException {
		if (socketChannel.connect(new InetSocketAddress(host, port))) {
			socketChannel.register(selector, SelectionKey.OP_READ);
			doWrite(socketChannel);
		} else {
			socketChannel.register(selector, SelectionKey.OP_CONNECT);
		}
	}

}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值