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



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



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
2.  
3. import
4. import
5. import
6. import
7. import
8. import
9. import
10. import
11. import
12.  
13.  
14. /** 
15.  * @author Yvon 
16.  *  
17.  */
18. public class
19.      
20. //Sun's NIO2 channel  implementation class  
21. private
22.      
23. //nio2 framework core data structure 
24.     PendingIoCache ioCache;  
25.  
26. //some field retrieve from sun channel implementation class  
27. private
28. private
29. private
30. private Field writeKilledF; // f 
31.  
32.     WindowsTransmitFileSupport()  
33.     {  
34. //dummy one for JNI code 
35.     }  
36.  
37. /** 
38.      *  
39.      */
40. public
41.             AsynchronousSocketChannel  
42.              channel) {  
43.  
44. this.channel = (WindowsAsynchronousSocketChannelImpl)channel;  
45. try
46. // Initialize the fields 
47. class
48. "ioCache");  
49. true);  
50.             ioCache = (PendingIoCache) f.get(channel);  
51. class
52. "writeLock");  
53. true);  
54.             writeLock = f.get(channel);  
55. class
56. "writing");  
57. true);  
58.  
59. class
60. "writeShutdown");  
61. true);  
62.  
63. class
64. "writeKilled");  
65. true);  
66.  
67. catch
68. // TODO Auto-generated catch block 
69.             e.printStackTrace();  
70. catch
71. // TODO Auto-generated catch block 
72.             e.printStackTrace();  
73. catch
74. // TODO Auto-generated catch block 
75.             e.printStackTrace();  
76. catch
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
88. private final
89. private final long file;//file is windows file HANDLE 
90.  
91. long
92. this.result = result;  
93. this.file = file;  
94.         }  
95.  
96.       
97.  
98. @Override
99. // @SuppressWarnings("unchecked") 
100. public void
101. long
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
113. if
114. // I/O is pending 
115. true;  
116. return;  
117.                 }  
118. if
119. // special case for shutdown output 
120. true;  
121. throw new
122.                 }  
123. // write completed immediately 
124. throw new InternalError("Write completed immediately");  
125. catch
126. // write failed. Enable writing before releasing waiters. 
127.                 channel.enableWriting();  
128. if (!shutdown && (x instanceof
129. new
130. if (!(x instanceof
131. new
132.                 result.setFailure(x);  
133. finally
134. // release resources if I/O not pending 
135. if
136. if
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
155.       
156.  
157. // release waiters if not already released by timeout 
158. synchronized
159. if
160. return;  
161.                 channel.enableWriting();  
162.  
163.                 result.setResult((V) Integer.valueOf(bytesTransferred));  
164.  
165.             }  
166. if
167.                 Invoker.invokeUnchecked(result);  
168. else
169.                 Invoker.invoke(result);  
170.             }  
171.         }  
172.  
173. @Override
174. public void failed(int
175. // return direct buffer to cache if substituted 
176.           
177.  
178. // release waiters if not already released by timeout 
179. if
180. new
181.  
182. synchronized
183. if
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
194. super
195.  
196. boolean closed = false;  
197. if
198. if (channel.remoteAddress == null)  
199. throw new
200.  
201.               
202. // check and update state 
203. synchronized
204. try{  
205. if
206. throw new
207. "Writing not allowed due to timeout or cancellation");  
208. if
209. throw new
210. if
211. true;  
212. else
213. true);  
214.                 }  
215. catch(Exception e)  
216.                 {  
217. new IllegalStateException(" catch exception when write");  
218.                     ise.initCause(e);  
219. throw
220.                 }  
221.             }  
222. else
223. true;  
224.         }  
225.  
226. // channel is closed or shutdown for write 
227. if
228. new
229. if (handler == null)  
230. return
231. null, e);  
232. return null;  
233.         }  
234.  
235.  
236.  
237. return
238.     }  
239.  
240.  
241. extends Number, A> Future implSendFile(long
242. super
243. // setup task 
244. new
245.                 attachment);  
246. new
247.         result.setContext(sendTask);  
248. // initiate I/O (can only be done from thread in thread pool) 
249. // initiate I/O 
250. if
251.             sendTask.run();  
252. else
253.             Invoker.invokeOnThreadInThreadPool(channel, sendTask);  
254.         }  
255. return
256.     }  
257.       
258. private native int transmitFile0(long handle, long
259. long
260.       
261. }  
262.


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

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

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

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

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