zoukankan      html  css  js  c++  java
  • Java NIO理解与使用

    Netty的使用或许我们看着官网user guide还是很容易入门的。因为java nio使用非常的繁琐,netty对java nio进行了大量的封装。对于Netty的理解,我们首先需要了解NIO的原理和使用。所以,我也特别渴望去了解NIO这种通信模式。

    官方的定义是:nio 是non-blocking的简称,在jdk1.4 里提供的新api 。Sun 官方标榜的特性如下: 为所有的原始类型提供(Buffer)缓存支持。字符集编码解码解决方案。 Channel :一个新的原始I/O 抽象。 支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。是不是很抽象?

    在阅读《NIO入门》这篇技术文档之后,收获了很多。包括对Java NIO的理解和使用,所以也特别的感谢作者。

    首先,还是来回顾以下从这篇文档中学到的要点。

    为什么要使用 NIO?
    NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。

    NIO最重要的组成部分

    通道 Channels
    缓冲区 Buffers
    选择器 Selectors


    Buffer 是一个对象, 它包含一些要写入或者刚读出的数据。

    在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。
    缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

    Channel是一个对象,可以通过它读取和写入数据

    看完下面这个例子,基本上就理解buffer和channel的作用了

    1. package yyf.java.nio.ibm;
    2. import java.io.*;
    3. import java.nio.*;
    4. import java.nio.channels.*;
    5. public class CopyFile {
    6. static public void main(String args[]) throws Exception {
    7. String infile = "c://test/nio_copy.txt";
    8. String outfile = "c://test/result.txt";
    9. FileInputStream fin = new FileInputStream(infile);
    10. FileOutputStream fout = new FileOutputStream(outfile);
    11. // 获取读的通道
    12. FileChannel fcin = fin.getChannel();
    13. // 获取写的通道
    14. FileChannel fcout = fout.getChannel();
    15. // 定义缓冲区,并指定大小
    16. ByteBuffer buffer = ByteBuffer.allocate(1024);
    17. while (true) {
    18. // 清空缓冲区
    19. buffer.clear();
    20. //从通道读取一个数据到缓冲区
    21. int r = fcin.read(buffer);
    22. //判断是否有从通道读到数据
    23. if (r == -1) {
    24. break;
    25. }
    26. //将buffer指针指向头部
    27. buffer.flip();
    28. //把缓冲区数据写入通道
    29. fcout.write(buffer);
    30. }
    31. }
    32. }

    缓冲区主要是三个变量

    position
    limit
    capacity
    这三个变量一起可以跟踪缓冲区的状态和它所包含的数据。我们将在下面的小节中详细分析每一个变量,还要介绍它们如何适应典型的读/写(输入/输出)进程。在这个例子中,我们假定要将数据从一个输入通道拷贝到一个输出通道。
    Position
    您可以回想一下,缓冲区实际上就是美化了的数组。在从通道读取时,您将所读取的数据放到底层的数组中。 position 变量跟踪已经写了多少数据。更准确地说,它指定了下一个字节将放到数组的哪一个元素中。因此,如果您从通道中读三个字节到缓冲区中,那么缓冲区的 position 将会设置为3,指向数组中第四个元素。
    同样,在写入通道时,您是从缓冲区中获取数据。 position 值跟踪从缓冲区中获取了多少数据。更准确地说,它指定下一个字节来自数组的哪一个元素。因此如果从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。
    Limit
    limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
    position 总是小于或者等于 limit。
    Capacity
    缓冲区的 capacity 表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。
    limit 决不能大于 capacity。

    缓冲区作为一个数组,这三个变量就是其中数据的标记,也很好理解。


    Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

    接下来来看看具体的使用把,我创建了一个直接收消息的服务器(一边接收一边写数据可能对于新手不好理解)

    服务端:

    1. package yyf.java.nio.test;
    2. import java.net.InetSocketAddress;
    3. import java.net.ServerSocket;
    4. import java.nio.ByteBuffer;
    5. import java.nio.channels.SelectionKey;
    6. import java.nio.channels.Selector;
    7. import java.nio.channels.ServerSocketChannel;
    8. import java.nio.channels.SocketChannel;
    9. import java.util.Iterator;
    10. import java.util.Set;
    11. public class NioReceiver {
    12. @SuppressWarnings("null")
    13. public static void main(String[] args) throws Exception {
    14. ByteBuffer echoBuffer = ByteBuffer.allocate(8);
    15. ServerSocketChannel ssc = ServerSocketChannel.open();
    16. Selector selector = Selector.open();
    17. ssc.configureBlocking(false);
    18. ServerSocket ss = ssc.socket();
    19. InetSocketAddress address = new InetSocketAddress(8080);
    20. ss.bind(address);
    21. SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
    22. System.out.println("开始监听……");
    23. while (true) {
    24. int num = selector.select();
    25. Set selectedKeys = selector.selectedKeys();
    26. Iterator it = selectedKeys.iterator();
    27. while (it.hasNext()) {
    28. SelectionKey sKey = (SelectionKey) it.next();
    29. SocketChannel channel = null;
    30. if (sKey.isAcceptable()) {
    31. ServerSocketChannel sc = (ServerSocketChannel) key.channel();
    32. channel = sc.accept();// 接受连接请求
    33. channel.configureBlocking(false);
    34. channel.register(selector, SelectionKey.OP_READ);
    35. it.remove();
    36. } else if (sKey.isReadable()) {
    37. channel = (SocketChannel) sKey.channel();
    38. while (true) {
    39. echoBuffer.clear();
    40. int r = channel.read(echoBuffer);
    41. if (r <= 0) {
    42. channel.close();
    43. System.out.println("接收完毕,断开连接");
    44. break;
    45. }
    46. System.out.println("##" + r + " " + new String(echoBuffer.array(), 0, echoBuffer.position()));
    47. echoBuffer.flip();
    48. }
    49. it.remove();
    50. } else {
    51. channel.close();
    52. }
    53. }
    54. }
    55. }
    56. }

    客户端(NIO):

    1. package yyf.java.nio.test;
    2. import java.net.InetSocketAddress;
    3. import java.nio.ByteBuffer;
    4. import java.nio.channels.SelectionKey;
    5. import java.nio.channels.Selector;
    6. import java.nio.channels.SocketChannel;
    7. import java.util.Iterator;
    8. import java.util.Set;
    9. public class NioTest {
    10. public static void main(String[] args) throws Exception {
    11. ByteBuffer echoBuffer = ByteBuffer.allocate(1024);
    12. SocketChannel channel = null;
    13. Selector selector = null;
    14. channel = SocketChannel.open();
    15. channel.configureBlocking(false);
    16. // 请求连接
    17. channel.connect(new InetSocketAddress("localhost", 8080));
    18. selector = Selector.open();
    19. channel.register(selector, SelectionKey.OP_CONNECT);
    20. int num = selector.select();
    21. Set selectedKeys = selector.selectedKeys();
    22. Iterator it = selectedKeys.iterator();
    23. while (it.hasNext()) {
    24. SelectionKey key = (SelectionKey) it.next();
    25. it.remove();
    26. if (key.isConnectable()) {
    27. if (channel.isConnectionPending()) {
    28. if (channel.finishConnect()) {
    29. // 只有当连接成功后才能注册OP_READ事件
    30. key.interestOps(SelectionKey.OP_READ);
    31. echoBuffer.put("123456789abcdefghijklmnopq".getBytes());
    32. echoBuffer.flip();
    33. System.out.println("##" + new String(echoBuffer.array()));
    34. channel.write(echoBuffer);
    35. System.out.println("写入完毕");
    36. } else {
    37. key.cancel();
    38. }
    39. }
    40. }
    41. }
    42. }
    43. }

    运行结果:

    1. 开始监听……
    2. ##8 12345678
    3. ##8 9abcdefg
    4. ##8 hijklmno
    5. ##2 pq
    6. 接收完毕,断开连接



    当然,BIO的客户端也可以,开启10个BIO客户端线程

    1. package yyf.java.nio.test;
    2. import java.io.ByteArrayOutputStream;
    3. import java.io.IOException;
    4. import java.io.InputStream;
    5. import java.io.OutputStream;
    6. import java.net.InetAddress;
    7. import java.net.InetSocketAddress;
    8. import java.net.Socket;
    9. import java.net.UnknownHostException;
    10. import java.util.Random;
    11. import yyf.java.test.Main;
    12. public class BioClientTest {
    13. public static void main(String[] args) throws Exception {
    14. BioClient n = new BioClient();
    15. for (int i = 0; i < 10; i++) {
    16. Thread t1 = new Thread(n);
    17. t1.start();
    18. }
    19. }
    20. }
    21. class BioClient implements Runnable {
    22. @Override
    23. public void run() {
    24. try {
    25. Socket socket = new Socket("127.0.0.1", 8080);
    26. OutputStream os = socket.getOutputStream();
    27. InputStream is = socket.getInputStream();
    28. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    29. String str = Thread.currentThread().getName() + "...........sadsadasJava";
    30. os.write(str.getBytes());
    31. StringBuffer sb = new StringBuffer();
    32. byte[] b = new byte[1024];
    33. int len;
    34. while ((len = is.read(b)) != -1) {
    35. bos.write(b, 0, len);
    36. }
    37. is.close();
    38. os.close();
    39. socket.close();
    40. System.out.println(Thread.currentThread().getName() + " 写入完毕 " + new String(bos.toByteArray()));
    41. } catch (Exception e) {
    42. e.printStackTrace();
    43. }
    44. }
    45. }
    运行结果:

    1. ##8 Thread-4
    2. ##8 ........
    3. ##8 ...sadsa
    4. ##7 dasJava
    5. 接收完毕,断开连接
    6. ##8 Thread-3
    7. ##8 ........
    8. ##8 ...sadsa
    9. ##7 dasJava
    10. 接收完毕,断开连接
    11. ##8 Thread-9
    12. ##8 ........
    13. ##8 ...sadsa
    14. ##7 dasJava
    15. 接收完毕,断开连接
    16. ##8 Thread-7
    17. ##8 ........
    18. ##8 ...sadsa
    19. ##7 dasJava
    20. 接收完毕,断开连接
    21. ##8 Thread-0
    22. ##8 ........
    23. ##8 ...sadsa
    24. ##7 dasJava
    25. 接收完毕,断开连接
    26. ##8 Thread-5
    27. ##8 ........
    28. ##8 ...sadsa
    29. ##7 dasJava
    30. 接收完毕,断开连接
    31. ##8 Thread-2
    32. ##8 ........
    33. ##8 ...sadsa
    34. ##7 dasJava
    35. 接收完毕,断开连接
    36. ##8 Thread-8
    37. ##8 ........
    38. ##8 ...sadsa
    39. ##7 dasJava
    40. 接收完毕,断开连接
    41. ##8 Thread-1
    42. ##8 ........
    43. ##8 ...sadsa
    44. ##7 dasJava
    45. 接收完毕,断开连接
    46. ##8 Thread-6
    47. ##8 ........
    48. ##8 ...sadsa
    49. ##7 dasJava
    50. 接收完毕,断开连接

    当然,这只是一个测试,对于一个服务器,是有读取,也有写出的,这是文档给的一个服务端例子

    1. package yyf.java.nio.ibm;
    2. import java.io.*;
    3. import java.net.*;
    4. import java.nio.*;
    5. import java.nio.channels.*;
    6. import java.util.*;
    7. public class MultiPortEcho {
    8. private int ports[];
    9. private ByteBuffer echoBuffer = ByteBuffer.allocate(5);
    10. public MultiPortEcho(int ports[]) throws IOException {
    11. this.ports = ports;
    12. go();
    13. }
    14. private void go() throws IOException {
    15. Selector selector = Selector.open();
    16. for (int i = 0; i < ports.length; ++i) {
    17. ServerSocketChannel ssc = ServerSocketChannel.open();
    18. ssc.configureBlocking(false);
    19. ServerSocket ss = ssc.socket();
    20. InetSocketAddress address = new InetSocketAddress(ports[i]);
    21. ss.bind(address);
    22. SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
    23. System.out.println("Going to listen on " + ports[i]);
    24. }
    25. while (true) {
    26. int num = selector.select();
    27. Set selectedKeys = selector.selectedKeys();
    28. Iterator it = selectedKeys.iterator();
    29. while (it.hasNext()) {
    30. SelectionKey key = (SelectionKey) it.next();
    31. if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
    32. ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
    33. SocketChannel sc = ssc.accept();
    34. sc.configureBlocking(false);
    35. SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
    36. it.remove();
    37. System.out.println("Got connection from " + sc);
    38. } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
    39. SocketChannel sc = (SocketChannel) key.channel();
    40. int bytesEchoed = 0;
    41. while (true) {
    42. echoBuffer.clear();
    43. int r = sc.read(echoBuffer);
    44. if (r <= 0) {
    45. sc.close();
    46. break;
    47. }
    48. echoBuffer.flip();
    49. sc.write(echoBuffer);
    50. bytesEchoed += r;
    51. }
    52. System.out.println("Echoed " + bytesEchoed + " from " + sc);
    53. it.remove();
    54. }
    55. }
    56. // System.out.println( "going to clear" );
    57. // selectedKeys.clear();
    58. // System.out.println( "cleared" );
    59. }
    60. }
    61. static public void main(String args[]) throws Exception {
    62. int ports[] = new int[] { 8080 };
    63. for (int i = 0; i < args.length; ++i) {
    64. ports[i] = Integer.parseInt(args[i]);
    65. }
    66. new MultiPortEcho(ports);
    67. }
    68. }

    现在,我们就写个客户端去跟服务器通信,把发过去的返回来:

    1. package yyf.java.nio;
    2. import java.io.IOException;
    3. import java.net.InetSocketAddress;
    4. import java.net.SocketAddress;
    5. import java.nio.ByteBuffer;
    6. import java.nio.channels.SocketChannel;
    7. import javax.swing.ButtonGroup;
    8. public class NioClient {
    9. public static void main(String[] args) throws IOException {
    10. SocketChannel socketChannel = SocketChannel.open();
    11. SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
    12. socketChannel.connect(socketAddress);
    13. String str = "你好a";
    14. ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
    15. socketChannel.write(buffer);
    16. socketChannel.socket().shutdownOutput();
    17. buffer.clear();
    18. byte[] bytes;
    19. int count = 0;
    20. while ((count = socketChannel.read(buffer)) > 0) {
    21. buffer.flip();
    22. bytes = new byte[count];
    23. buffer.get(bytes);
    24. System.out.println(new String(buffer.array()));
    25. buffer.clear();
    26. }
    27. socketChannel.socket().shutdownInput();
    28. socketChannel.socket().close();
    29. socketChannel.close();
    30. }
    31. }


    运行结果

    server:

    1. Going to listen on 8080
    2. Got connection from java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:63584]
    3. Echoed 7 from java.nio.channels.SocketChannel[closed]

    client:

    你好a
    

    对于NIO的入门就先到这里。







  • 相关阅读:
    servlet.txt笔记
    用数组实现集合的功能
    用父类声明的变量和用接口声明的变量的区别
    DHTML_____document对象的方法
    DHTML_____window对象的事件
    DHTML_____window对象属性
    DHTML_____window对象方法
    DHTML_____如何编写事件处理程序
    常用点击事件(鼠标、光标、键盘、body)
    鼠标滑动显示不同页面的效果——————获取鼠标相对于整个页面的坐标
  • 原文地址:https://www.cnblogs.com/jpfss/p/9257185.html
Copyright © 2011-2022 走看看