关于阻塞与非阻塞:http://www.javashuo.com/article/p-qrzhqjkj-mh.htmlhtml
当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其余任务。java
所以,在网络通讯进行 IO 操做时,因为线程会阻塞,因此服务器端必须为每一个客户端都提供一个独立的线程进行处理,当服务器端须要处理大量客户端时,性能急剧降低。服务器
package nio; import org.junit.Test; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Scanner; public class TestBlockingNIO { // 客户端 @Test public void client() throws IOException { // 获取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); // 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 发送到服务端 Scanner scan = new Scanner(System.in); while (scan.hasNext()) { String str = scan.next(); buf.put(str.getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); if ("exit".equals(str)) { break; } } // 接收服务端的反馈 sChannel.shutdownOutput(); int len = 0; while ((len = sChannel.read(buf)) != -1) { buf.flip(); System.out.println(new String(buf.array(), 0, len)); buf.clear(); } // 关闭通道 sChannel.close(); } // 服务端 @Test public void server() throws IOException { // 获取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); // 绑定链接 ssChannel.bind(new InetSocketAddress(9898)); retry: while (true) { // 获取客户端链接的通道 SocketChannel sChannel = ssChannel.accept(); // 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 接收客户端的数据 while (sChannel.read(buf) != -1) { String str = new String(buf.array()).trim(); if ("exit".equals(str)) { break retry; } buf.flip(); System.out.println(str); buf.clear(); } // 发送反馈给客户端 buf.put("服务端接收数据成功!".getBytes()); buf.flip(); sChannel.write(buf); // 关闭通道 sChannel.close(); } // 关闭通道 ssChannel.close(); } }
当线程从某通道进行读写数据时,若没有数据可用时,该线程能够进行其余任务。网络
线程一般将非阻塞 IO 的空闲时间用于在其余通道上执行 IO 操做,因此单独的线程能够管理多个输入和输出通道。ide
所以,NIO 可让服务器端使用一个或有限几个线程来同时处理链接到服务器端的全部客户端。性能
注:NIO 的 IO 行为仍是同步的。spa
/* * 使用 NIO 完成网络通讯的三个核心: * * 1. 通道(Channel):负责链接 * java.nio.channels.Channel 接口: * |--SelectableChannel * |--SocketChannel * |--ServerSocketChannel * |--DatagramChannel * * |--Pipe.SinkChannel * |--Pipe.SourceChannel * * 2. 缓冲区(Buffer):负责数据的存取 * * 3. 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 情况 * 能够监听的事件类型(可以使用 SelectionKey 的四个常量表示) * 读: SelectionKey.OP_READ (1) * 写: SelectionKey.OP_WRITE (4) * 链接: SelectionKey.OP_CONNECT(8) * 接收: SelectionKey.OP_ACCEPT (16) * * Selector 经常使用方法 * Set<SelectionKey> keys():全部的 SelectionKey 集合。表明注册在该 Selector上的 Channel * selectedKeys():被选择的 SelectionKey 集合。返回此Selector的已选择键集 * intselect():监控全部注册的 Channel,当它们中间有须要处理的 IO 操做时,该方法返回,并将对应得的 SelectionKey 加入被选择的 SelectionKey 集合中,该方法返回这些 Channel 的数量。 * int select(long timeout):能够设置超时时长的 select() 操做 * int selectNow():执行一个当即返回的 select() 操做,该方法不会阻塞线程 * Selector wakeup():使一个还未返回的 select() 方法当即返回 * void close():关闭该选择器 */
package nio; import org.junit.Test; 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.Scanner; public class TestNonBlockingNIO { //客户端 @Test public void client() throws IOException { // 获取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); // 切换非阻塞模式 sChannel.configureBlocking(false); // 分配指定大小的缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 发送数据给服务端 Scanner scan = new Scanner(System.in); while (scan.hasNext()) { String str = scan.next(); buf.put((new Date().toString() + "\n" + str).getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); } // 关闭通道 sChannel.close(); } //服务端 @Test public void server() throws IOException { // 获取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); // 切换非阻塞模式 ssChannel.configureBlocking(false); // 绑定链接 ssChannel.bind(new InetSocketAddress(9898)); // 获取选择器 Selector selector = Selector.open(); // 将通道注册到选择器上, 而且指定“监听接收事件” ssChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ); // 轮询式的获取选择器上已经“准备就绪”的事件 while (selector.select() > 0) { // 获取当前选择器中全部注册的“选择键(已就绪的监听事件)” Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { // 获取准备“就绪”的是事件 SelectionKey sk = it.next(); // 判断具体是什么事件准备就绪 if (sk.isAcceptable()) { // 若“接收就绪”,获取客户端链接 SocketChannel sChannel = ssChannel.accept(); // 切换非阻塞模式 sChannel.configureBlocking(false); // 将该通道注册到选择器上 sChannel.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { // 获取当前选择器上“读就绪”状态的通道 SocketChannel sChannel = (SocketChannel) sk.channel(); // 读取数据 ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; while ((len = sChannel.read(buf)) > 0) { buf.flip(); System.out.println(new String(buf.array(), 0, len)); buf.clear(); } } // 移除当前 SelectionKey it.remove(); } } } }
package nio; import org.junit.Test; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Date; import java.util.Iterator; import java.util.Scanner; public class TestNonBlockingNIO2 { @Test public void send() throws IOException{ DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); ByteBuffer buf = ByteBuffer.allocate(1024); Scanner scan = new Scanner(System.in); while(scan.hasNext()){ String str = scan.next(); buf.put((new Date().toString() + ":\n" + str).getBytes()); buf.flip(); dc.send(buf, new InetSocketAddress("127.0.0.1", 9898)); buf.clear(); } dc.close(); } @Test public void receive() throws IOException{ DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); dc.bind(new InetSocketAddress(9898)); Selector selector = Selector.open(); dc.register(selector, SelectionKey.OP_READ); while(selector.select() > 0){ Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){ SelectionKey sk = it.next(); if(sk.isReadable()){ ByteBuffer buf = ByteBuffer.allocate(1024); dc.receive(buf); buf.flip(); System.out.println(new String(buf.array(), 0, buf.limit())); buf.clear(); } } it.remove(); } } }
Java NIO 管道是 2 个线程之间的单向数据链接。Pipe 有一个 source 通道和一个 sink 通道。数据会被写到 sink 通道,从 source 通道读取。.net
package nio; import org.junit.Test; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Pipe; public class TestPipe { @Test public void test() throws IOException { // 获取管道 Pipe pipe = Pipe.open(); ByteBuffer buf = ByteBuffer.allocate(1024); buf.put("经过单向管道发送数据".getBytes()); buf.flip(); // 将缓冲区中的数据写入管道 Pipe.SinkChannel sinkChannel = pipe.sink(); sinkChannel.write(buf); ByteBuffer buf2 = ByteBuffer.allocate(1024); // 读取缓冲区中的数据 Pipe.SourceChannel sourceChannel = pipe.source(); int len = sourceChannel.read(buf2); System.out.println(new String(buf2.array(), 0, len)); sourceChannel.close(); sinkChannel.close(); } }