Java网络编程----NIO编程

古城微笑少年丶 2022-06-01 09:29 451阅读 0赞

一.介绍

有人叫NIO为New IO,有的人把NIO叫做Nonblock IO,这里还是比较倾向于后者,非阻塞IO.NIO有几个重要的基本概念:Buffer(缓冲区),Channel(管道,通道),Selector(选择器,多路复用器).NIO的本质就是避免原始的TCP建立连接使用三次握手的操作,减少连接的开销.模型如下图:

Center

1.1 Buffer

Buffer是一个对象,它包括一些要写入或要读取的数据.在NIO类库中加入Buffer对象,体现了新库与原IO的一个重要区别 .在面向流的IO中,可以将数据直接写入或读取到Stream对象中.在NIO库中,所有数据都是用缓冲区处理的(读写).缓冲区实质上是一个数组,通常它是一个字节数组(ByteBuffer),也可以使用其他类型的数组.这个数组为缓冲区提供了数据的访问读写等操作属性,如位置,容量,上限等概念.

Buffer类型:我们最常用的就是ByteBuffer,实际上每种Java基本类型都对应了一种缓冲区(出了Boolean类型)

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

LongBuffer

FloatBuffer

DoubleBuffer

  1. package com.east.nio;
  2. import java.nio.IntBuffer;
  3. /**
  4. *
  5. * @author
  6. */
  7. public class TestBuffer {
  8. public static void main(String[] args) {
  9. // TODO Auto-generated method stub
  10. //1.基本操作
  11. //创建指定长度的缓冲区
  12. IntBuffer buf = IntBuffer.allocate(10);
  13. buf.put(13); //往缓冲区放元素
  14. buf.put(21);
  15. buf.put(35);
  16. //把位置复位为0,也就是position位置: 3-->0
  17. buf.flip();
  18. System.out.println("使用filp复位: " + buf);
  19. System.out.println("容量为 : " + buf.capacity()); //容量一旦初始化后不允许改变(Wrap方法包裹数组除外)
  20. System.out.println("限制为 : " + buf.limit()); //由于只装载了三个元素,所以可读取或者操作的元素为3,即limit = 3
  21. System.out.println("获取下标为1的元素:" + buf.get(1));
  22. System.out.println("get(index)方法,position位置不改变:" + buf);
  23. buf.put(1,4);
  24. System.out.println("put(index,change)方法,position位置不变: " + buf);
  25. for(int i=0; i<buf.limit(); i++){
  26. //调用get方法会使其缓冲区位置(position)向后递增一位
  27. System.out.println(buf.get() + "\t");
  28. }
  29. System.out.println("buf对象遍历之后为:" + buf);
  30. //2 wrap方法使用
  31. //wrap方法会包裹一个数组:一般这种用法不会先初始化缓存对象的长度,因为没有意义,最后还会被wrap所包裹的数组覆盖掉
  32. //并且wrap方法修改缓冲区对象的时候,数组本身也会跟着发生变化
  33. int [] arr = new int [] {1,2,5};
  34. IntBuffer buf1 = IntBuffer.wrap(arr);
  35. System.out.println(buf1);
  36. IntBuffer buf2 = IntBuffer.wrap(arr, 0, 2);
  37. //这样使用表示容量为数组arr的长度,但是可操作的元素只有实际进入缓冲区的元素长度
  38. System.out.println(buf2);
  39. //3.其他方法
  40. IntBuffer buf1 = IntBuffer.allocate(10);
  41. int [] arr = new int []{1,2,5};
  42. buf1.put(arr);
  43. System.out.println(buf1);
  44. //一种复制方法
  45. IntBuffer buf3 = buf1.duplicate();
  46. System.out.println(buf3);
  47. //设置buf1的位置属性
  48. //buf1.position(0);
  49. buf1.flip();
  50. System.out.println(buf1);
  51. System.out.println("可读数据为: " + buf1.remaining());
  52. int [] arr2 = new int [buf1.remaining()];
  53. //将缓冲区数据放入到arr2数组中去
  54. buf1.get(arr2);
  55. for(int i : arr2){
  56. System.out.print(Integer.toString(i) + ",");
  57. }
  58. }
  59. }

1.2 Channel

通道(Channel),它就像自来水管道一样,网络数据通过Channel读取和写入,通道与流的不同之处在于通道是双向的,而流只是一个方向上移动(一个流必须是InputStream或者OutputStream的子类),而通道可以用于读,写或者二者同时进行,最关键的是可以与多路复用器结合起来,有多种的状态位,方便多路复用器去识别.事实上通道分为两大类,一类是网络读写(SelectableChannel),一类用于文件操作(FileChannel),我们使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子类

1.3 Selector(一)

多路复用器(Selector),它NIO编程的基础,非常重要.多路复用器提供选择已经就绪的任务的能力.简单地说,就是Selector会不断地轮询注册在其上的通道(Channel),如果某个通道发生了读写操作,这个通道就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以取得就绪的Selector集合,从而进行后续的IO操作.

一个多路复用器(Selector)可以负责成千上万的Channel通道,没有上限.这也是JDK使用了epoll代替了传统的select实现,获得连接句柄没有限制.这也意味着我们只要一个线程负责Selector的轮询,就可以接入成千上万的客户端,这也是JDK NIO库的巨大进步.

1.4 Selector(二)

Selector线程就类似一个管理者(Master),管理了成千上万个管道,然后轮询那个管道的数据已经准备好,通知CPU执行IO的读取和写入操作.

Selector模式:当IO事件(管道)注册到选择器以后,Selector会分配给每个管道一个Key值,相当于标签.Selector选择器是以轮询的方式进行查找注册的所有IO事件(管道),当我们的IO事件(管道)准备就绪后,Select就会识别,会通过Key值来找到相应的管道,进行相关的数据处理操作(从管道里读或写数据,写到我们的数据缓冲区中)

每个管道都会对选择器进行注册不同的事件状态,以便选择器查找,

SelectionKey.OP_CONTENT

SelectionKey.OP_ACCEPT

SelectionKey.OP_READ

SelectionKey.OP_WRITE

二.NIO编程示例

  1. package com.east.nio.modou;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  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. public class Server implements Runnable {
  11. //1.多路复用器(管理所有的通道)
  12. private Selector selector;
  13. //2.建立缓冲区
  14. private ByteBuffer readBuf = ByteBuffer.allocate(1024);
  15. //3.
  16. private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
  17. public Server(int port) {
  18. try {
  19. //1.打开多路复用器
  20. this.selector = Selector.open();
  21. //2.打开服务器通道
  22. ServerSocketChannel ssc = ServerSocketChannel.open();
  23. //3.设置服务器通道为非阻塞模式
  24. ssc.configureBlocking(false);
  25. //4.绑定地址
  26. ssc.bind(new InetSocketAddress(port));
  27. //5.把服务器通道注册到多路复用器上,并且监听阻塞事件
  28. ssc.register(this.selector, SelectionKey.OP_ACCEPT);
  29. System.out.println("Server Start,Port :" + port);
  30. } catch (IOException e) {
  31. // TODO Auto-generated catch block
  32. e.printStackTrace();
  33. }
  34. // TODO Auto-generated constructor stub
  35. }
  36. @Override
  37. public void run() {
  38. // TODO Auto-generated method stub
  39. while(true){
  40. try {
  41. //1.必须要让多路复用器开始监听
  42. this.selector.select();
  43. //2.返回多路复用器已经选择的结果集
  44. Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
  45. //3.进行遍历
  46. while(keys.hasNext()){
  47. //4.获取一个选择的元素
  48. SelectionKey key = keys.next();
  49. //5.如果有.则直接从容器中移除
  50. keys.remove();
  51. //6.如果是有限的
  52. if(key.isValid()){
  53. //7.如果为阻塞状态
  54. if(key.isAcceptable()){
  55. this.accept(key);
  56. }
  57. //8.如果为可读状态
  58. if(key.isReadable()){
  59. this.read(key);
  60. }
  61. //9.写数据
  62. if(key.isWritable()){
  63. //this.write(key); //ssc
  64. }
  65. }
  66. }
  67. } catch (IOException e) {
  68. // TODO Auto-generated catch block
  69. e.printStackTrace();
  70. }
  71. }
  72. }
  73. private void accept(SelectionKey key){
  74. try {
  75. //1.获取服务通道
  76. ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
  77. //2.执行阻塞方法(等待客户端的通道)
  78. SocketChannel sc = ssc.accept();
  79. //3.设置阻塞模式
  80. sc.configureBlocking(false);
  81. //4.注册到多路复用器上,并设置读取标识
  82. sc.register(this.selector, SelectionKey.OP_READ);
  83. } catch (IOException e) {
  84. // TODO Auto-generated catch block
  85. e.printStackTrace();
  86. }
  87. }
  88. private void read(SelectionKey key){
  89. try {
  90. //1.清空缓冲区旧的数据
  91. this.readBuf.clear();
  92. //2.获取之前注册的socket通道对象
  93. SocketChannel sc = (SocketChannel) key.channel();
  94. //3.读取数据
  95. int count = sc.read(this.readBuf);
  96. //4.如果没有数据
  97. if(count == -1){
  98. key.channel().close();
  99. key.cancel();
  100. return ;
  101. }
  102. //5.有数据则进行读取,读取之前需要进行复位方法(把poition和limit进行复位)
  103. this.readBuf.flip();
  104. //6.根据缓冲区的数据长度创建相应大小的byte数组,接受缓冲区数据
  105. byte [] bytes = new byte[this.readBuf.remaining()];
  106. //7.接受缓冲区数据
  107. this.readBuf.get(bytes);
  108. //8.打印结果
  109. String body = new String(bytes).trim();
  110. System.out.println("Server :" + body);
  111. //9.可以给客户端写数据...
  112. } catch (Exception e) {
  113. // TODO Auto-generated catch block
  114. e.printStackTrace();
  115. }
  116. }
  117. public static void main(String[] args) {
  118. new Thread(new Server(8765)).start();
  119. }
  120. }
  121. package com.east.nio.modou;
  122. import java.io.IOException;
  123. import java.net.InetSocketAddress;
  124. import java.nio.ByteBuffer;
  125. import java.nio.channels.SocketChannel;
  126. /**
  127. * 使用NIO实现一边写数据,一边读数据
  128. * @author lhy
  129. *
  130. */
  131. public class Client {
  132. //需要一个Selector
  133. public static void main(String[] args) {
  134. // TODO Auto-generated method stub
  135. //创建连接的地址
  136. InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765);
  137. //声明连接通道
  138. SocketChannel sc = null;
  139. //建立缓冲区
  140. ByteBuffer buf = ByteBuffer.allocate(1024);
  141. try {
  142. //打开通道
  143. sc = SocketChannel.open();
  144. //进行连接
  145. sc.connect(address);
  146. while(true){
  147. //定义一个字节数组
  148. byte [] bytes = new byte [1024];
  149. System.in.read(bytes);
  150. /*
  151. * 关键的步骤
  152. */
  153. //把数据放到缓冲区中
  154. buf.put(bytes);
  155. //对缓冲区进行复位
  156. buf.flip();
  157. //写出数据
  158. sc.write(buf);
  159. //清空缓冲区数据
  160. buf.clear();
  161. }
  162. } catch (IOException e) {
  163. // TODO Auto-generated catch block
  164. e.printStackTrace();
  165. } finally {
  166. if(sc != null){
  167. try {
  168. sc.close();
  169. } catch (IOException e) {
  170. // TODO Auto-generated catch block
  171. e.printStackTrace();
  172. }
  173. }
  174. }
  175. }
  176. }

发表评论

表情:
评论列表 (有 0 条评论,451人围观)

还没有评论,来说两句吧...

相关阅读

    相关 网络编程NIO(二)

    一.NIO定义     Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线