初试Java 7 NIO2:实现高性能的HTTP Server

开发 后端
NIO.2是针对Java中I/O功能的一系列增强,计划在Java 7中发布。在现在的Java 7里程碑版本中已经可以使用这个功能,本文作者描述了自己利用NIO2特性实现高性能Java HTTP Server的方法。

本文来自DoubleH的BlogJava博客,原文标题为《JDK7 NIO2 实践: 增加 TransmitFile支持》。对于Java 7 NIO2特性的更多描述,可参考以前Google的一次技术演讲

#t#JDK7的NIO2特性或许是我最期待的,我一直想基于它写一个高性能的Java Http Server.现在这个想法终于可以实施了。

本人基于目前最新的JDK7 b76开发了一个HTTP Server性能确实不错。

在windows平台上NIO2采用AccpetEx来异步接受连接,并且读写全都关联到IOCP完成端口。不仅如此,为了方便开发者使用,连IOCP工作线程都封装好了,你只要提供线程池就OK。

但是要注意,IOCP工作线程的线程池必须是 Fix的,因为你发出的读写请求都关联到相应的线程上,如果线程死了,那读写完成情况是不知道的。

作为一个Http Server,传送文件是必不可少的功能,那一般文件的传送都是要把程序里的buffer拷贝到内核的buffer,由内核发送出去的。windows平台上为这种情况提供了很好的解决方案,使用TransmitFile接口

  1. BOOL TransmitFile(  
  2.     SOCKET hSocket,  
  3.     HANDLE hFile,  
  4.     DWORD nNumberOfBytesToWrite,  
  5.     DWORD nNumberOfBytesPerSend,  
  6.     LPOVERLAPPED lpOverlapped,  
  7.     LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,  
  8.     DWORD dwFlags  
  9. ); 

你只要把文件句柄发送给内核就行了,内核帮你搞定其余的,真正做到Zero-Copy.

但是很不幸,NIO2里AsynchronousSocketChannel没有提供这样的支持。而为HTTP Server的性能考量,本人只好自己增加这个支持。

要无缝支持,这个必须得表现的跟 Read /Write一样,有完成的通知,通知传送多少数据,等等。

仔细读完sun的IOCP实现以后发现这部分工作他们封装得很好,基本只要往他们的框架里加东西就好了。

为了能访问他们的框架代码,我定义自己的TransmitFile支持类在sun.nio.ch包里,以获得最大的权限。

  1. package sun.nio.ch;  
  2.  
  3. import java.io.IOException;  
  4. import java.lang.reflect.Field;  
  5. import java.nio.channels.AsynchronousCloseException;  
  6. import java.nio.channels.AsynchronousSocketChannel;  
  7. import java.nio.channels.ClosedChannelException;  
  8. import java.nio.channels.CompletionHandler;  
  9. import java.nio.channels.NotYetConnectedException;  
  10. import java.nio.channels.WritePendingException;  
  11. import java.util.concurrent.Future;  
  12.  
  13.  
  14. /**  
  15.  * @author Yvon  
  16.  *   
  17.  */ 
  18. public class WindowsTransmitFileSupport {  
  19.      
  20.    //Sun's NIO2 channel  implementation class   
  21.     private WindowsAsynchronousSocketChannelImpl channel;  
  22.      
  23.     //nio2 framework core data structure  
  24.     PendingIoCache ioCache;  
  25.  
  26.    //some field retrieve from sun channel implementation class   
  27.     private Object writeLock;  
  28.     private Field writingF;  
  29.     private Field writeShutdownF;  
  30.     private Field writeKilledF; // f  
  31.  
  32.     WindowsTransmitFileSupport()  
  33.     {  
  34.         //dummy one for JNI code  
  35.     }  
  36.  
  37.     /**  
  38.      *   
  39.      */ 
  40.     public WindowsTransmitFileSupport(  
  41.             AsynchronousSocketChannel  
  42.              channel) {  
  43.  
  44.         this.channel = (WindowsAsynchronousSocketChannelImpl)channel;  
  45.         try {  
  46.         // Initialize the fields  
  47.             Field f = WindowsAsynchronousSocketChannelImpl.class 
  48.                     .getDeclaredField("ioCache");  
  49.             f.setAccessible(true);  
  50.             ioCache = (PendingIoCache) f.get(channel);  
  51.             f = AsynchronousSocketChannelImpl.class 
  52.                     .getDeclaredField("writeLock");  
  53.             f.setAccessible(true);  
  54.             writeLock = f.get(channel);  
  55.             writingF = AsynchronousSocketChannelImpl.class 
  56.                     .getDeclaredField("writing");  
  57.             writingF.setAccessible(true);  
  58.  
  59.             writeShutdownF = AsynchronousSocketChannelImpl.class 
  60.                     .getDeclaredField("writeShutdown");  
  61.             writeShutdownF.setAccessible(true);  
  62.  
  63.             writeKilledF = AsynchronousSocketChannelImpl.class 
  64.                     .getDeclaredField("writeKilled");  
  65.             writeKilledF.setAccessible(true);  
  66.  
  67.         } catch (NoSuchFieldException e) {  
  68.             // TODO Auto-generated catch block  
  69.             e.printStackTrace();  
  70.         } catch (SecurityException e) {  
  71.             // TODO Auto-generated catch block  
  72.             e.printStackTrace();  
  73.         } catch (IllegalArgumentException e) {  
  74.             // TODO Auto-generated catch block  
  75.             e.printStackTrace();  
  76.         } catch (IllegalAccessException e) {  
  77.             // TODO Auto-generated catch block  
  78.             e.printStackTrace();  
  79.         }  
  80.     }  
  81.  
  82.       
  83.     /**  
  84.      * Implements the task to initiate a write and the handler to consume the  
  85.      * result when the send file completes.  
  86.      */ 
  87.     private class SendFileTask implements Runnable, Iocp.ResultHandler {  
  88.         private final PendingFuture result;  
  89.         private final long file;//file is windows file HANDLE  
  90.  
  91.         SendFileTask(long file, PendingFuture result) {  
  92.             this.result = result;  
  93.             this.file = file;  
  94.         }  
  95.  
  96.       
  97.  
  98.         @Override 
  99.         // @SuppressWarnings("unchecked")  
  100.         public void run() {  
  101.             long overlapped = 0L;  
  102.             boolean pending = false;  
  103.             boolean shutdown = false;  
  104.  
  105.             try {  
  106.                 channel.begin();  
  107.  
  108.           
  109.  
  110.                 // get an OVERLAPPED structure (from the cache or allocate)  
  111.                 overlapped = ioCache.add(result);  
  112.                 int n = transmitFile0(channel.handle, file, overlapped);  
  113.                 if (n == IOStatus.UNAVAILABLE) {  
  114.                     // I/O is pending  
  115.                     pending = true;  
  116.                     return;  
  117.                 }  
  118.                 if (n == IOStatus.EOF) {  
  119.                     // special case for shutdown output  
  120.                     shutdown = true;  
  121.                     throw new ClosedChannelException();  
  122.                 }  
  123.                 // write completed immediately  
  124.                 throw new InternalError("Write completed immediately");  
  125.             } catch (Throwable x) {  
  126.                 // write failed. Enable writing before releasing waiters.  
  127.                 channel.enableWriting();  
  128.                 if (!shutdown && (x instanceof ClosedChannelException))  
  129.                     x = new AsynchronousCloseException();  
  130.                 if (!(x instanceof IOException))  
  131.                     x = new IOException(x);  
  132.                 result.setFailure(x);  
  133.             } finally {  
  134.                 // release resources if I/O not pending  
  135.                 if (!pending) {  
  136.                     if (overlapped != 0L)  
  137.                         ioCache.remove(overlapped);  
  138.                   
  139.                 }  
  140.                 channel.end();  
  141.             }  
  142.  
  143.             // invoke completion handler  
  144.             Invoker.invoke(result);  
  145.         }  
  146.  
  147.           
  148.  
  149.         /**  
  150.          * Executed when the I/O has completed  
  151.          */ 
  152.         @Override 
  153.         @SuppressWarnings("unchecked")  
  154.         public void completed(int bytesTransferred, boolean canInvokeDirect) {  
  155.       
  156.  
  157.             // release waiters if not already released by timeout  
  158.             synchronized (result) {  
  159.                 if (result.isDone())  
  160.                     return;  
  161.                 channel.enableWriting();  
  162.  
  163.                 result.setResult((V) Integer.valueOf(bytesTransferred));  
  164.  
  165.             }  
  166.             if (canInvokeDirect) {  
  167.                 Invoker.invokeUnchecked(result);  
  168.             } else {  
  169.                 Invoker.invoke(result);  
  170.             }  
  171.         }  
  172.  
  173.         @Override 
  174.         public void failed(int error, IOException x) {  
  175.             // return direct buffer to cache if substituted  
  176.           
  177.  
  178.             // release waiters if not already released by timeout  
  179.             if (!channel.isOpen())  
  180.                 x = new AsynchronousCloseException();  
  181.  
  182.             synchronized (result) {  
  183.                 if (result.isDone())  
  184.                     return;  
  185.                 channel.enableWriting();  
  186.                 result.setFailure(x);  
  187.             }  
  188.             Invoker.invoke(result);  
  189.         }  
  190.  
  191.     }  
  192.  
  193.     public extends Number, A> Future sendFile(long file, A att,  
  194.             CompletionHandlersuper A> handler) {  
  195.  
  196.         boolean closed = false;  
  197.         if (channel.isOpen()) {  
  198.             if (channel.remoteAddress == null)  
  199.                 throw new NotYetConnectedException();  
  200.  
  201.               
  202.             // check and update state  
  203.             synchronized (writeLock) {  
  204.                 try{  
  205.                 if (writeKilledF.getBoolean(channel))  
  206.                     throw new IllegalStateException(  
  207.                             "Writing not allowed due to timeout or cancellation");  
  208.                 if (writingF.getBoolean(channel))  
  209.                     throw new WritePendingException();  
  210.                 if (writeShutdownF.getBoolean(channel)) {  
  211.                     closed = true;  
  212.                 } else {  
  213.                     writingF.setBoolean(channel, true);  
  214.                 }  
  215.                 }catch(Exception e)  
  216.                 {  
  217.                     IllegalStateException ise=new IllegalStateException(" catch exception when write");  
  218.                     ise.initCause(e);  
  219.                     throw ise;  
  220.                 }  
  221.             }  
  222.         } else {  
  223.             closed = true;  
  224.         }  
  225.  
  226.         // channel is closed or shutdown for write  
  227.         if (closed) {  
  228.             Throwable e = new ClosedChannelException();  
  229.             if (handler == null)  
  230.                 return CompletedFuture.withFailure(e);  
  231.             Invoker.invoke(channel, handler, att, null, e);  
  232.             return null;  
  233.         }  
  234.  
  235.  
  236.  
  237.         return implSendFile(file,att,handler);  
  238.     }  
  239.  
  240.  
  241.     extends Number, A> Future implSendFile(long file, A attachment,  
  242.             CompletionHandlersuper A> handler) {  
  243.         // setup task  
  244.         PendingFuture result = new PendingFuture(channel, handler,  
  245.                 attachment);  
  246.         SendFileTask sendTask=new SendFileTask(file,result);  
  247.         result.setContext(sendTask);  
  248.         // initiate I/O (can only be done from thread in thread pool)  
  249.         // initiate I/O  
  250.         if (Iocp.supportsThreadAgnosticIo()) {  
  251.             sendTask.run();  
  252.         } else {  
  253.             Invoker.invokeOnThreadInThreadPool(channel, sendTask);  
  254.         }  
  255.         return result;  
  256.     }  
  257.       
  258.     private native int transmitFile0(long handle, long file,  
  259.             long overlapped);  
  260.       
  261. }  
  262.  

这个操作跟默认实现的里的write操作是很像的,只是最后调用的本地方法不一样。。

接下来,我们怎么使用呢,这个类是定义在sun的包里的,直接用的话,会报IllegalAccessError,因为我们的类加载器跟初始化加载器是不一样的。

解决办法一个是通过启动参数-Xbootclasspath,让我们的包被初始加载器加载。我个人不喜欢这种办法,所以就采用JNI来定义我们的windows TransmitFile支持类。

这样我们的工作算是完成了,注意,发送文件的时候传得是文件句柄,这样做的好处是你可以更好的控制,一般是在发送前,打开文件句柄,完成后在回调通知方法里关闭文件句柄。

有兴趣的同学可以看看我的HTTP server项目:

http://code.google.com/p/jabhttpd/

目前基本功能实现得差不多,做了些简单的测试,性能比较满意。这个服务器不打算支持servlet api,基本是专门给做基于长连接模式通信的定做的。

责任编辑:yangsai 来源: BlogJava
相关推荐

2009-12-14 10:44:51

Java 7NIO2

2011-12-15 11:39:25

JavaNIO

2020-03-11 10:09:57

JAVA详解classpath

2011-03-11 09:51:47

Java NIO

2023-07-12 08:24:19

Java NIO通道

2011-12-15 13:28:57

2015-11-10 09:25:05

HTTP2提升性能

2019-03-27 10:50:50

HTTP请求管线式

2011-12-15 09:55:47

javanio

2023-09-06 12:01:50

HTTP协议信息

2011-12-13 09:12:34

JavaNIO

2023-07-28 08:23:05

选择器Java NIO

2011-04-07 13:39:24

WebHTTP

2024-09-06 07:55:42

2024-03-20 08:00:00

软件开发Java编程语言

2021-07-27 16:01:29

高并发定时器高性能

2024-07-31 08:31:13

2019-04-08 10:09:04

CPU缓存高性能

2021-08-12 16:42:09

WireGuardWindows内核NT

2024-02-26 07:43:10

大语言模型LLM推理框架
点赞
收藏

51CTO技术栈公众号