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

来源:互联网 发布:胸大是怎样的体验知乎 编辑:程序博客网 时间:2024/05/30 05:01
http://developer.51cto.com/art/200912/169217.htm
 
2009-11-30 09:40 DoubleH BlogJava 我要评论(1) 字号:T |T
一键收藏,随时查看,分享好友!

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

AD:


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

    • Apache HTTP Server与Tomcat的三种连接方
    • JDK6.0的新特性:轻量级Http Server
    • Java 7决定包含闭包 发布日期推迟至2010
    • Java 7已经完成的七大新功能预览
    • Java 7新功能代码范例
    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 <V&NBSP;< SPAN>extends Number, A> Future sendFile(long file, A att,  
    194.             CompletionHandler<V,&NBSP;?&NBSP;< SPAN>super 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.     <V&NBSP;< SPAN>extends Number, A> Future implSendFile(long file, A attachment,  
    242.             CompletionHandler<V,&NBSP;?&NBSP;< SPAN>super 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,基本是专门给做基于长连接模式通信的定做的。

    原创粉丝点击