[关闭]
@coldxiangyu 2017-07-13T10:20:27.000000Z 字数 3761 阅读 2082

关于ByteBuffer中allocate与allocateDirect的区别

源码系列


前言

在使用NIO对现有纯Socket项目进行改造的过程中,遇到了诸多问题。也正是因为NIO在实际应用中困难重重,所以大部分架构师基本上采用netty、mina这种非常成熟的框架。但是NIO本身就显得轻量多了,很多问题我们也是通过查看NIO的源码进行问题的解决的。

这篇文章是探究ByteBuffer中allocate和allocateDirector源码级别的区别。

探究

下面是我模拟单线程监听N个端口的服务端代码:

  1. package com.sinosoft.midplat.net;
  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.nio.charset.Charset;
  10. import java.util.Iterator;
  11. /**
  12. * Created by coldxiangyu on 2017/7/7.
  13. */
  14. public class ChannelServer {
  15. private static final int BUF_SIZE=1024;
  16. private static int[] PORTS;
  17. private static final int TIMEOUT = 3000;
  18. public static void main(String[] args) {
  19. PORTS = new int[]{60970, 60950, 60940, 60930, 60840 ,60111};
  20. selector();
  21. }
  22. public static void handleAccept(SelectionKey key) throws IOException{
  23. ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
  24. SocketChannel sc = ssChannel.accept();
  25. sc.configureBlocking(false);
  26. sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocateDirect(BUF_SIZE));
  27. }
  28. public static void handleRead(SelectionKey key) throws IOException{
  29. //Charset cn = Charset.forName("GBK");
  30. SocketChannel sc = (SocketChannel)key.channel();
  31. ByteBuffer buf = (ByteBuffer)key.attachment();
  32. buf.clear();
  33. long bytesRead = sc.read(buf);
  34. if(bytesRead == -1){
  35. sc.close();
  36. }
  37. /*while(bytesRead > 0){
  38. buf.flip();
  39. while(buf.hasRemaining()){
  40. //System.out.print(buf.get());
  41. System.out.print(cn.decode(buf));
  42. }
  43. System.out.println();
  44. buf.clear();
  45. bytesRead = sc.read(buf);
  46. }*/
  47. else{
  48. buf.flip();
  49. byte[] data = buf.array();
  50. String msg = new String(data).trim();
  51. System.out.println("客户端:" + msg);
  52. ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
  53. //将消息回送给客户端
  54. sc.write(outBuffer);
  55. key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
  56. }
  57. }
  58. public static void handleWrite(SelectionKey key) throws IOException{
  59. System.out.println("begin to write now!");
  60. ByteBuffer buf = (ByteBuffer)key.attachment();
  61. buf.flip();
  62. SocketChannel sc = (SocketChannel) key.channel();
  63. while(buf.hasRemaining()){
  64. sc.write(buf);
  65. }
  66. buf.compact();
  67. }
  68. public static void selector() {
  69. Selector selector = null;
  70. ServerSocketChannel ssc = null;
  71. try{
  72. selector = Selector.open();
  73. for (int i=0; i<PORTS.length; i++) {
  74. ssc = ServerSocketChannel.open();
  75. ssc.socket().bind(new InetSocketAddress(PORTS[i]));
  76. ssc.configureBlocking(false);
  77. ssc.register(selector, SelectionKey.OP_ACCEPT);
  78. System.out.println("开始监听端口:" + PORTS[i] + "...");
  79. }
  80. while(true){
  81. if(selector.select(TIMEOUT) == 0){
  82. //System.out.println("==");
  83. continue;
  84. }
  85. Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
  86. while(iter.hasNext()){
  87. SelectionKey key = iter.next();
  88. try{
  89. if(key.isAcceptable()){
  90. handleAccept(key);
  91. }
  92. if(key.isReadable()){
  93. handleRead(key);
  94. }
  95. if(key.isWritable() && key.isValid()){
  96. handleWrite(key);
  97. }
  98. if(key.isConnectable()){
  99. System.out.println("isConnectable = true");
  100. }
  101. } catch (Exception e) {
  102. e.printStackTrace();
  103. } finally {
  104. iter.remove();
  105. key.cancel();
  106. }
  107. }
  108. }
  109. }catch(IOException e){
  110. e.printStackTrace();
  111. }
  112. }
  113. }

在客户端请求的时候,接收报文出错:

image_1bkr1r63ej351g3q9bg1qoj11749.png-26.4kB

这个时候没有别的办法,只能通过看源码来解决了。
首先找到异常抛出位置,在ByteBuffer类的array方法中:

image_1bkr1v2nr1spivmjssi1dt01hkam.png-49.5kB

可以看到,在hb==null的时候抛出的此异常。我们再进一步探索。
而这个hb是在ByteBuffer中定义的final byte数组,作为内存缓冲区。整个类只有在构造方法中对此赋值,如下图:

image_1bksotruqk70he26ss161o1gm9.png-48.3kB

这时候就要看ByteBuffer初始化的时候为什么没有初始化hb了。我们在自己的程序中看一下ByteBuffer是在哪里进行初始化的。如下图:

image_1bksp6p58jm81iah1f3p13uekitm.png-48.9kB

我们在注册channel的时候,以allocateDirector的形式携带了一个BUF_SIZE的ByteBuffer,我们来看看allocateDirector方法的实现:

image_1bkspbkgktebva11vm3cldmjc13.png-22.8kB

可以看到它是返回一个DirectByteBuffer对象,它的构造方法如下:

image_1bkspf7udp5i5mg1i8j1p6tgll1g.png-63.2kB

它通过super对父类初始化,调用了父类MappedByteBuffer的构造方法:

image_1bkspidegln2vb69181ogtpml1t.png-19.4kB

而MappedByteBuffer又是ByteBuffer的子类,它通过super初始化ByteBuffer另外一个构造方法:

image_1bksppip258l1snm1tfdor1uq22a.png-39.5kB

到这里就很明显了,Byte[] hb参数传递了null,于是我们找到了原因。

那我们再来看看ByteBuffer的另一个allocate方法:

image_1bkspvt8luua14on17vf10cm9712n.png-25.1kB

它返回了一个HeapByteBuffer对象,而HeapByteBuffer是ByteBuffer的直接子类,通过super直接对ByteBuffer进行初始化:

image_1bksq33h11jva1nlf26j1efd1ulg34.png-24.9kB

我们可以看到,这次初始化直接传递了new byte[cap]参数,对hb直接赋值:

image_1bksq61te191bm1217hjikn10j83h.png-24kB

我们将allocateDirector换成allocate,并启动服务端进行验证:

image_1bksqd74rcil2eckff14b41ps14o.png-53kB

执行客户端程序:

image_1bksqf0pa15o815og1e6qud91bro55.png-30.8kB

成功接收到客户端发送报文,并无报错。

至此,问题得以解决。

总结

在之前对于ByteBuffer的allocate和allocateDirector的认识,只是停留在allocateDirector可以直接操作操作系统的缓冲区,效率要比allocate添加用户缓冲区要快,在实际应用中,其实还有很多细节上的区别。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注