初试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,基本是专门给做基于长连接模式通信的定做的。
















