Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1523
  • 博文数量: 8
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 90
  • 用 户 组: 普通用户
  • 注册时间: 2023-07-18 09:31
文章分类

全部博文(8)

文章存档

2023年(8)

我的朋友
最近访客

分类: Java

2023-07-20 15:59:47

从业务开发的角度来看,应该很少有业务场景在使用NIO了。毕竟消息中间件用起来其实就应该满足诉求了。但如果是中间件开发同学应该比较了解这个。我其实在业务开发的过程中就从来没有用过,随手写写,不考虑业务实战情况。

QUICKSTART

其实重点主要是3个   Channel, Buffer和Selector(熟悉linux C语言的开发的,应该知道有个select函数,很久以前看过)。
  • Channel:渠道,其实理解就是和linux文件定义差不多,能产生和消费信息的都可以抽象成channel,比如文件,网络,键盘(能产生信息),显示器终端(能消费信息)。
  • Buffer:缓冲区。
  • Selector:如果知道linux select函数的就很好理解了,简单理解就是我都NIO了,我就不等你channel准备好,把消息发给我了。我先去忙其它的,等你准备好了,就通过selector来通知我,我再腾出手来处理你给我的消息。
比如说,当前我监听8080端口,基于TCP链接,接收别人给我发消息。然后处理这个消息。传统的做法,比如我接收到一个来自外面的连接请求,然后建立一个线程去管理这个链接,这个线程负责从连接上获取数据,并处理数据,主线程在这里只负责监听端口,等有请求过来就创立子线程去处理。换一种做法,我主线程既负责监听端口,也负责管理连接,监听连接上是否有数据过来。也就是我同时在处理多个事情,{BANNED}中国第一个,我监听端口是否有连接请求,第二个,有了多个连接请求后,我监听这些连接上是否有数据,我接收到数据并进行处理。下面上代码:

服务端代码:

点击(此处)折叠或打开

  1. package sty.zchi;

  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.ServerSocketChannel;
  7. import java.nio.channels.SocketChannel;
  8. import java.nio.charset.StandardCharsets;
  9. import java.util.Set;

  10. /**
  11.  * Hello world!
  12.  *
  13.  */
  14. public class StyServerSocket {
  15.     public static void main( String[] args ) {
  16.         try {
  17.             //开始监听8080号端口,并且是非阻塞式
  18.             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  19.             serverSocketChannel.configureBlocking(false);
  20.             serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8080));
  21.             Selector selector = Selector.open();
  22.             //将端口的OP_ACCEPT事件注册给selector
  23.             serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  24.             while(true) {
  25.                 /**
  26.                  * 开始监控selector上所有的时间,注意这里是阻塞式的,主线程到这里如果没有事情发生,就会阻塞住。
  27.                  * 另外如果是循环第一次进来,此时selector上只有一个8080的OP_ACCEPT在监控
  28.                  */
  29.                 selector.select();
  30.                 Set<SelectionKey> selectionKeys = selector.selectedKeys();
  31.                 selectionKeys.forEach(key -> {
  32.                     try {
  33.                         final SocketChannel client;
  34.                         if (key.isAcceptable()) {
  35.                             //说明有人连进来
  36.                             System.out.println("some one connect server");
  37.                             ServerSocketChannel server = (ServerSocketChannel) key.channel();
  38.                             //接受8080上的连接请求,并且也设置成非阻塞式,然后将连接的OP_READ事件注册到selector上
  39.                             client = serverSocketChannel.accept();
  40.                             client.configureBlocking(false);
  41.                             client.register(selector, SelectionKey.OP_READ);
  42.                         } else if(key.isReadable()) {
  43.                             client = (SocketChannel) key.channel();
  44.                             ByteBuffer readBuffer = ByteBuffer.allocate(1024);
  45.                             int count = client.read(readBuffer);
  46.                             if(count > 0) {
  47.                                 readBuffer.flip();
  48.                                 String receiveMessage = String.valueOf(StandardCharsets.UTF_8.decode(readBuffer).array());
  49.                                 System.out.println("receive message = " + receiveMessage);
  50.                             }
  51.                             client.close();
  52.                         }
  53.                     } catch (Exception e) {
  54.                         e.printStackTrace();
  55.                     } finally {
  56.                         selectionKeys.clear();
  57.                     }
  58.                 });
  59.             }
  60.         } catch (Exception e) {
  61.             e.printStackTrace();
  62.         }
  63.     }

  64. }


客户端代码:

点击(此处)折叠或打开

  1. package sty.zchi;

  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import java.nio.channels.SocketChannel;
  5. import java.nio.charset.StandardCharsets;
  6. import java.util.Scanner;

  7. public class StyClient {
  8.     public static void main(String[] args) {
  9.         try {
  10.             SocketChannel socketChannel = SocketChannel.open();
  11.             socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
  12.             ByteBuffer writeBuffer = ByteBuffer.allocate(32);

  13.             while(true) {
  14.                 Scanner sc = new Scanner(System.in);
  15.                 String str = sc.nextLine();
  16.                 if(str.equals("quit")) {
  17.                     socketChannel.close();
  18.                     return;
  19.                 }
  20.                 writeBuffer.put(str.getBytes(StandardCharsets.UTF_8));
  21.                 writeBuffer.flip();
  22.                 socketChannel.write(writeBuffer);
  23.                 writeBuffer.clear();
  24.             }

  25.         } catch (Exception e) {
  26.             e.printStackTrace();
  27.         }
  28.     }
  29. }
基本上看代码注释就明白了,其实业务实际上也没这么简单。我这里只是个开门的样例,下面具体看看后面的内容。

CHANNEL

中文名叫通道,有人说类似于JAVA语言中的流,但我觉得其实更相似的类比应该是linux中关于文件的定义既:能产生或者消费信息的东西。所以文件,网络端口,甚至是键盘,显示器都可以这定。上面的例子是个TCP网络套接字(SOCKET的channel)。其实我们还有FileChannel, UDP协议的DatagramChannel等。
注意是的通道能产生和消费信息,这个过程是不能直接交互的,需要通过下面的BUFFER来中转。

BUFFER

中文名叫缓冲区,既然是用于交换信息的缓冲区,首先从类型上有(我理解是数据类型)
  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • FloatBuffer
  • DoubleBuffer
  • LongBuffer
很容易理解不是么。说几个关键的方法操作

关键属性和数据结构理解

缓冲区是既能读也能写的,那么就需要一个结构来支持,我写数据,我也能读出数据出来,设计的思想就不赘述了,我直接写核心的属性
  • capacity:这个好理解,就是这个这个缓冲区最大容量,注意不是可用容量。他在allocate之后就分配了,当然可以改。除非你手动改,不然就是allocate分配后就是个固定值。
  • position:这个表示下一次操作的位置,这个操作可以是读,也可以是写。比如我现在分配了一个10char容量的缓冲区,那么position就是0,这标识我下次写操作就写到0号位。如果我没写之前就不讲武德的开始做get操作呢,我试了下,没有报错,就是个乱码。当然你实际业务不会出这种事情。
  • limit:标识实际可以操作数据的大小。是不是有点晕,实际操作大小不就是capacity么,怎么又冒出了个limit呢。下面讲读写切换filp时候就明白了。
开始讲翻转吧,我们设想下,我申请了一个10 char位置的charBuffer。刚申请完,就是往里面写啦。这个时候我们理解下:
  • capacity:这个就不解释了,肯定是10,怎么弄都是10.
  • position:这个好理解,肯定是0,我写的第一个字符肯定放在第0位
  • limit:容量是10么,我写肯定最多能写10个,所以就是10.
好了,我们向里面写了6个数据,这个时候容量肯定是10,position肯定是6,因为我写了6个数据占满了0,1,2,3,4,5 所以下一个能写操作就是6号位,limit还是10,毕竟一共能操作的是10个。

接下来,我就开始想读数据,此时想想,对于读操作来说,你的position应该是0,limit应该是6,capacity应该是10。那是不是需要我们去修改这些属性呢(有方法可以改的,但没必要)。我们只用调用flip来进行读写转换。这就是为什么每次在对缓冲区做读写操作之前,如果上一次操作和这次操作有变化,就需要调用flip的原因。那我们写个代码试试。

点击(此处)折叠或打开

  1. package sty.zchi;

  2. import java.nio.CharBuffer;

  3. public class BufferTest {
  4.     public static void main(String[] args) {
  5.         CharBuffer charBuffer = CharBuffer.allocate(10);
  6.         System.out.println("刚初始化的charBuffer关键属性 position = " + charBuffer.position()
  7.                 + " limit = " + charBuffer.limit() + " capacity = " + charBuffer.capacity());

  8.         System.out.println("不讲武德的操作,啥都没写,就开始读 结果是" + charBuffer.get());

  9.         System.out.println("在不讲武德的一次写操作后,charBuffer关键属性position = " + charBuffer.position()
  10.                 + " limit = " + charBuffer.limit() + " capacity = " + charBuffer.capacity());

  11.         charBuffer.clear();
  12.         System.out.println("好吧不讲武德的操作影响了我们属性,我们clear一把后,看看charBuffer关键属性position = " + charBuffer.position()
  13.                 + " limit = " + charBuffer.limit() + " capacity = " + charBuffer.capacity());

  14.         char[] charArray = {'0', '1', '2', '3', '4', '5'};
  15.         charBuffer.put(charArray);
  16.         System.out.println("在放入6个char数据后charBuffer关键属性 position = " + charBuffer.position()
  17.                 + " limit = " + charBuffer.limit() + " capacity = " + charBuffer.capacity());

  18.         charBuffer.flip();
  19.         System.out.println("在放入6个char数据后charBuffer经过翻转后关键属性 position = " + charBuffer.position()
  20.                 + " limit = " + charBuffer.limit() + " capacity = " + charBuffer.capacity());
  21.     }
  22. }

好吧运行结果是

我理解BUFFER将的也挺明白的了。

阅读(68) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:JAVA字节码动态代理AOP

给主人留下些什么吧!~~