Java NIO通信基础示例详解
田埂 人气:0Java NIO 通信基础介绍
高性能的 Java 通信,绝对离不开 Java NIO 技术,现在主流的技术框架或中间件服务器,都使 用了 Java NIO 技术,譬如:Tomcat、Jetty、Netty。
Java NIO 由以下三个核心组件组成:
- Channel(通道)
- Buffer(缓冲区)
- Selector(选择器)
NIO 和 OIO 的对比
在 Java 中,NIO 和 OIO 的区别,主要体现在三个方面:
- OIO 是面向流(Stream Oriented)的,NIO 是面向缓冲区(Buffer Oriented)的。 何谓面向流,何谓面向缓冲区呢? OIO 是面向字节流或字符流的,在一般的 OIO 操作中,我们以流式的方式顺序地从一个流中读取一个或多个字节,因此,我们不能随意地改变读取指针的位置。而在 NIO 操作中则不同,NIO 中引入了 Channel(通道)和 Buffer(缓冲区)的概念。读取和写入,只需要从通道中读取数据到缓冲区中,或将数据从缓冲区中写入到通道中。NIO 不像 OIO 那样是顺序操作,可以随意地读取 Buffer 中任意位置的数据。
- OIO 的操作是阻塞的,而 NIO 的操作是非阻塞的。 NIO 如何做到非阻塞的呢?大家都知道,OIO 操作都是阻塞的,例如,我们调用一个 read 方法读取一个文件的内容,那么调用 read 的线程会被阻塞住,直到 read 操作完成。 而在 NIO 的非阻塞模式中,当我们调用 read 方法时,如果此时有数据,则 read 读取数据并返回;如果此时没有数据,则 read 直接返回,而不会阻塞当前线程。NIO 的非阻塞,是如何做到的呢?NIO 使用了通道和通道的多路复用技术。
- OIO 没有选择器(Selector)概念,而 NIO 有选择器的概念。 NIO 的实现,是基于底层的选择器的系统调用。NIO 的选择器,需要底层操作系统提供支持。 而 OIO 不需要用到选择器。
使用 FileChannel 完成文件复制的实践案例
import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class MyCopyFile { private File inFile; private File outFile; private FileInputStream fis = null; private FileOutputStream fos = null; private FileChannel fisChannel = null; private FileChannel fosChannel = null; //复制文件 public void copyFile(String srcPath, String destPath) throws IOException { try { inFile = new File(srcPath); outFile = new File(destPath); fis = new FileInputStream(inFile); fos = new FileOutputStream(outFile); fisChannel = fis.getChannel(); fosChannel = fos.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int length = -1; while ((length = fisChannel.read(buffer)) != -1) { buffer.flip(); int outLenth = 0; while ((outLenth = fosChannel.write(buffer)) != 0) { System.out.println("读取的字节数为:" + outLenth); } buffer.clear(); } //强制刷新磁盘 fosChannel.force(true); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { fosChannel.close(); fos.close(); fisChannel.close(); fis.close(); } } public static void main(String[] args) throws IOException { MyCopyFiletest = new MyCopyFile(); String s1 = "D:\\maze.txt"; String s2 = "D:\\maze1.txt"; MyCopyFile.copyFile(s1, s2); } }
使用 DatagramChannel 数据包通道发送数据的实践案例
功能:
获取用户的输入数据,通过 DatagramChannel 数据报通道,将数据发送到远程的服务器。
客户端代码:
public class Client { //Client发送信息 public void send() throws IOException { DatagramChannel dChannel = DatagramChannel.open(); dChannel.configureBlocking(false); ByteBuffer buf = ByteBuffer.allocate(1024); Scanner sc = new Scanner(System.in); while (sc.hasNext()) { String s = sc.nextLine(); buf.put(s.getBytes()); buf.flip(); dChannel.send(buf, new InetSocketAddress("127.0.0.1", 9999)); buf.clear(); } dChannel.close(); } public static void main(String[] args) throws IOException { new Client().send(); } }
服务端代码:
public class Server { //服务端接收 用户发来的信息 public void receive() throws IOException { DatagramChannel serverChannel = DatagramChannel.open(); //设置成非阻塞模式 serverChannel.configureBlocking(false); serverChannel.bind(new InetSocketAddress("127.0.0.1", 9999)); Selector selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_READ); while (selector.select() > 0) { Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); ByteBuffer buffer = ByteBuffer.allocate(1024); while (iterator.hasNext()) { SelectionKey next = iterator.next(); if (next.isReadable()) { SocketAddress receive = serverChannel.receive(buffer); buffer.flip(); String s = new String(buffer.array(), 0, buffer.limit()); System.out.println(s); buffer.clear(); } } iterator.remove(); } //关闭选择器和通道 selector.close(); serverChannel.close(); } public static void main(String[] args) throws IOException { new Server().receive(); } }
使用 NIO 实现 Discard 服务器的实践案例
功能:
仅仅读取客户端通道的输入数据,读取完成后直接关闭客户端通道;并且读取到的数据直接抛弃掉
Discard 服务器代码:
public class SocketServerDemo { public void receive() throws IOException { //创建服务器的通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //设置为非阻塞模式 serverSocketChannel.configureBlocking(false); //开启选择器 Selector selector = Selector.open(); //绑定链接 serverSocketChannel.bind(new InetSocketAddress(9999)); //将通道的某个IO事件 注册到选择器上 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //轮询所有就绪的IO事件 while (selector.select() > 0) { //逐个获取IO事件 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); //逐个判断该IO事件是否为想要的 while (iterator.hasNext()) { SelectionKey next = iterator.next(); if (next.isAcceptable()) { //如果为该事件为“连接就绪”事件,就获取客户端的链接 SocketChannel clientSocket = serverSocketChannel.accept(); //将客户端的链接设置为非阻塞模式 clientSocket.configureBlocking(false); //将新的通道的可读事件,注册到选择器上 clientSocket.register(selector, SelectionKey.OP_READ); } else if (next.isReadable()) { //若IO事件为“可读事件”,读取数据 SocketChannel clientSocket = (SocketChannel) next.channel(); //创建缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); int length = 0; //读取事件 让后丢弃 while ((length = clientSocket.read(buffer)) > 0) { buffer.flip(); String s = new String(buffer.array(), 0, length); System.out.println(s); buffer.clear(); } clientSocket.close(); } //移除选择键 iterator.remove(); } } serverSocketChannel.close(); } public static void main(String[] args) throws IOException { new SocketServerDemo().receive(); } }
客户端的 DiscardClient 代码:
public class SocketClientDemo { public void socketClient() throws IOException { SocketChannel clientSocket = SocketChannel.open(new InetSocketAddress(9999)); //切换成非阻塞模式 clientSocket.configureBlocking(false); //如果没有连接完成 就一直链接 while (!clientSocket.finishConnect()){ } //执行到这里说明已经连接完成了 ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put("Hello SocketService".getBytes()); buffer.flip(); clientSocket.write(buffer); clientSocket.shutdownInput(); clientSocket.close(); } public static void main(String[] args) throws IOException { new SocketClientDemo().socketClient(); } }
与 Java OIO 相比,Java NIO 编程大致的特点如下:
(1)在 NIO 中,服务器接收新连接的工作,是异步进行的。不像 Java 的 OIO 那样,服务器监听连接,是同步的、阻塞的。NIO 可以通过选择器(也可以说成:多路复用器),后续不断地轮询选择器的选择键集合,选择新到来的连接。
(2)在 NIO 中,SocketChannel 传输通道的读写操作都是异步的。如果没有可读写的数据,负责 IO 通信的线程不会同步等待。这样,线程就可以处理其他连接的通道;不需要像 OIO 那样,线程一直阻塞,等待所负责的连接可用为止。
(3)在 NIO 中,一个选择器线程可以同时处理成千上万个客户端连接,性能不会随着客户端的增加而线性下降。
加载全部内容