基于JDK7 NIO2的高性能web服务器实践之二
前一篇博客,我简单提了下怎么为NIO2增加TransmitFile支持,文件传送吞吐量是一个性能关注点,此外,并发连接数也是重要的关注点。
不过JDK7中又一次做了简单的实现,不支持同时投递多个AcceptEx请求,只支持一次一个,返回后再投递。这样,客户端连接的接受速度必然大打折扣。不知道为什么sun会做这样的实现,WSASend()/WSAReceive()一次只允许一个还是可以理解,毕竟简化了编程,不用考虑封包乱序问题。
也降低了内存耗尽的风险。AcceptEx却没有这样的理由了。
于是再一次为了性能,我增加了同时投递多个的支持。
另外,在JDK7的默认实现中,AcceptEx返回后,为了设置远程和本地InetSocketAddress也采用了效率很低的方法。4次通过JNI调用getsockname,2次为了取sockaddr,2次为了取port. 这些操作本人采用GetAcceptExSockaddrs一次完成,进一步提高效率。
先看Java部分的代码,框架跟JDK7的一样,细节处理不一样:
对应的CPP代码如下:
/*
* Class: sun_nio_ch_WindowsMultiAcceptSupport
* Method: updateAcceptContext
* Signature: (JJ[Ljava/net/InetAddress;[IJ)V
*/
JNIEXPORT void JNICALL Java_sun_nio_ch_WindowsMultiAcceptSupport_updateAcceptContext
(JNIEnv * env , jclass clazz, jlong listenSocket, jlong acceptSocket, jobjectArray sockArray,jintArray portArray,jlong buf)
{
SOCKET s1 = (SOCKET)jlong_to_ptr(listenSocket);
SOCKET s2 = (SOCKET)jlong_to_ptr(acceptSocket);
PVOID outputBuffer = (PVOID)jlong_to_ptr(buf);
INT iLocalAddrLen = 0 ;
INT iRemoteAddrLen = 0 ;
SOCKETADDRESS * lpLocalAddr;
SOCKETADDRESS * lpRemoteAddr;
jobject localAddr;
jobject remoteAddr;
jint ports[ 2 ] = { 0 };
setsockopt(s2, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, ( char * ) & s1, sizeof (s1));
(lpGetAcceptExSockaddrs)(outputBuffer,
0 ,
sizeof (SOCKETADDRESS) + 16 ,
sizeof (SOCKETADDRESS) + 16 ,
(LPSOCKADDR * ) & lpLocalAddr,
& iLocalAddrLen,
(LPSOCKADDR * ) & lpRemoteAddr,
& iRemoteAddrLen);
localAddr = lpNET_SockaddrToInetAddress(env,( struct sockaddr * )lpLocalAddr,( int * )ports);
remoteAddr = lpNET_SockaddrToInetAddress(env,( struct sockaddr * )lpRemoteAddr,( int * )(ports + 1 ));
env -> SetObjectArrayElement(sockArray, 0 ,localAddr);
env -> SetObjectArrayElement(sockArray, 1 ,remoteAddr);
env -> SetIntArrayRegion(portArray, 0 , 2 ,ports);
}
/*
* Class: sun_nio_ch_WindowsMultiAcceptSupport
* Method: accept0
* Signature: (JJJJ)I
*/
jint JNICALL Java_sun_nio_ch_WindowsMultiAcceptSupport_accept0
(JNIEnv * env, jclass clazz, jlong listenSocket, jlong acceptSocket, jlong ov, jlong buf)
{
BOOL res;
SOCKET s1 = (SOCKET)jlong_to_ptr(listenSocket);
SOCKET s2 = (SOCKET)jlong_to_ptr(acceptSocket);
PVOID outputBuffer = (PVOID)jlong_to_ptr(buf);
DWORD nread = 0 ;
OVERLAPPED * lpOverlapped = (OVERLAPPED * )jlong_to_ptr(ov);
ZeroMemory((PVOID)lpOverlapped, sizeof (OVERLAPPED));
// why use SOCKETADDRESS?
// because client may use IPv6 to connect to server.
res = (lpAcceptEx)(s1,
s2,
outputBuffer,
0 ,
sizeof (SOCKETADDRESS) + 16 ,
sizeof (SOCKETADDRESS) + 16 ,
& nread,
lpOverlapped);
if (res == 0 ) {
int error = WSAGetLastError();
if (error == ERROR_IO_PENDING) {
return NIO2_IOS_UNAVAILABLE;
}
return NIO2_THROWN;
}
return 0 ;
}
这里用到的lpNET_SockaddrToInetAddress是JDK7中NET.DLL暴露的方法,从DLL里加载。相应代码如下:
细心的同学可能会发现,在创建socket之前没有初始化WinSock库,因为在这段代码前,我初始化了一个InetSocketAddress对象,这样JVM会加载NET.DLL并初始化WinSock库了。
OK,现在,你可以在支持类上同时发起多个AcceptEx请求了。
PS:基于这个我简单测试了下我的服务器,同时开5000个线程,每个下载3M多点的文件,一分钟内能够全部正确完成。
服务器正在开发中,有兴趣的请加入:http://code.google.com/p/jabhttpd
不过JDK7中又一次做了简单的实现,不支持同时投递多个AcceptEx请求,只支持一次一个,返回后再投递。这样,客户端连接的接受速度必然大打折扣。不知道为什么sun会做这样的实现,WSASend()/WSAReceive()一次只允许一个还是可以理解,毕竟简化了编程,不用考虑封包乱序问题。
也降低了内存耗尽的风险。AcceptEx却没有这样的理由了。
于是再一次为了性能,我增加了同时投递多个的支持。
另外,在JDK7的默认实现中,AcceptEx返回后,为了设置远程和本地InetSocketAddress也采用了效率很低的方法。4次通过JNI调用getsockname,2次为了取sockaddr,2次为了取port. 这些操作本人采用GetAcceptExSockaddrs一次完成,进一步提高效率。
先看Java部分的代码,框架跟JDK7的一样,细节处理不一样:
/**
*
*/
package sun.nio.ch;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.AcceptPendingException;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.CompletionHandler;
import java.nio.channels.NotYetBoundException;
import java.nio.channels.ShutdownChannelGroupException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import sun.misc.Unsafe;
/**
* This class enable multiple 'AcceptEx' post on the completion port, hence improve the concurrent connection number.
* @author Yvon
*
*/
public class WindowsMultiAcceptSupport {
WindowsAsynchronousServerSocketChannelImpl schannel;
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 2 * (sizeof(SOCKET_ADDRESS) + 16)
private static final int ONE_DATA_BUFFER_SIZE = 88 ;
private long handle;
private Iocp iocp;
// typically there will be zero, or one I/O operations pending. In rare
// cases there may be more. These rare cases arise when a sequence of accept
// operations complete immediately and handled by the initiating thread.
// The corresponding OVERLAPPED cannot be reused/released until the completion
// event has been posted.
private PendingIoCache ioCache;
private Queue < Long > dataBuffers;
// the data buffer to receive the local/remote socket address
// private final long dataBuffer;
private AtomicInteger pendingAccept;
private int maxPending;
Method updateAcceptContextM;
Method acceptM;
WindowsMultiAcceptSupport() {
// dummy for JNI code.
}
public void close() throws IOException {
schannel.close();
for ( int i = 0 ; i < maxPending + 1 ; i ++ ) // assert there is maxPending+1 buffer in the queue
{
long addr = dataBuffers.poll();
// release resources
unsafe.freeMemory(addr);
}
}
/**
*
*/
public WindowsMultiAcceptSupport(AsynchronousServerSocketChannel ch, int maxPost) {
if (maxPost <= 0 || maxPost > 1024 )
throw new IllegalStateException( " maxPost can't less than 1 and greater than 1024 " );
this .schannel = (WindowsAsynchronousServerSocketChannelImpl) ch;
maxPending = maxPost;
dataBuffers = new ConcurrentLinkedQueue < Long > ();
for ( int i = 0 ; i < maxPending + 1 ; i ++ ) {
dataBuffers.add(unsafe.allocateMemory(ONE_DATA_BUFFER_SIZE));
}
pendingAccept = new AtomicInteger( 0 );
try {
Field f = WindowsAsynchronousServerSocketChannelImpl. class .getDeclaredField( " handle " );
f.setAccessible( true );
handle = f.getLong(schannel);
f = WindowsAsynchronousServerSocketChannelImpl. class .getDeclaredField( " iocp " );
f.setAccessible( true );
iocp = (Iocp) f.get(schannel);
f = WindowsAsynchronousServerSocketChannelImpl. class .getDeclaredField( " ioCache " );
f.setAccessible( true );
ioCache = (PendingIoCache) f.get(schannel);
f = WindowsAsynchronousServerSocketChannelImpl. class .getDeclaredField( " accepting " );
f.setAccessible( true );
AtomicBoolean accepting = (AtomicBoolean) f.get(schannel);
accepting.set( true ); // disable accepting by origin channel.
} catch (Exception e) {
e.printStackTrace();
}
}
@SuppressWarnings( " unchecked " )
public final < A > void accept(A attachment,
CompletionHandler < AsynchronousSocketChannel, ? super A > handler) {
if (handler == null )
throw new NullPointerException( " 'handler' is null " );
implAccept(attachment, (CompletionHandler < AsynchronousSocketChannel, Object > ) handler);
}
/**
* Task to initiate accept operation and to handle result.
*/
private class AcceptTask implements Runnable, Iocp.ResultHandler {
private final WindowsAsynchronousSocketChannelImpl channel;
private final AccessControlContext acc;
private final PendingFuture < AsynchronousSocketChannel, Object > result;
private final long dataBuffer;
AcceptTask(WindowsAsynchronousSocketChannelImpl channel, AccessControlContext acc,
long dataBuffer, PendingFuture < AsynchronousSocketChannel, Object > result) {
this .channel = channel;
this .acc = acc;
this .result = result;
this .dataBuffer = dataBuffer;
}
void enableAccept() {
pendingAccept.decrementAndGet();
dataBuffers.add(dataBuffer);
}
void closeChildChannel() {
try {
channel.close();
} catch (IOException ignore) {
}
}
// caller must have acquired read lock for the listener and child channel.
void finishAccept() throws IOException {
/**
* JDK7 use 4 calls to getsockname to setup
* local& remote address, this is very inefficient.
*
* I change this to use GetAcceptExSockaddrs
*/
InetAddress[] socks = new InetAddress[ 2 ];
int [] ports = new int [ 2 ];
updateAcceptContext(handle, channel.handle(), socks, ports, dataBuffer);
InetSocketAddress local = new InetSocketAddress(socks[ 0 ], ports[ 0 ]);
final InetSocketAddress remote = new InetSocketAddress(socks[ 1 ], ports[ 1 ]);
channel.setConnected(local, remote);
// permission check (in context of initiating thread)
if (acc != null ) {
AccessController.doPrivileged( new PrivilegedAction < Void > () {
public Void run() {
SecurityManager sm = System.getSecurityManager();
sm.checkAccept(remote.getAddress().getHostAddress(), remote.getPort());
return null ;
}
}, acc);
}
}
/**
* Initiates the accept operation.
*/
@Override
public void run() {
long overlapped = 0L ;
try {
// begin usage of listener socket
schannel.begin();
try {
// begin usage of child socket (as it is registered with
// completion port and so may be closed in the event that
// the group is forcefully closed).
channel.begin();
synchronized (result) {
overlapped = ioCache.add(result);
int n = accept0(handle, channel.handle(), overlapped, dataBuffer); // Be careful for the buffer address
if (n == IOStatus.UNAVAILABLE) {
return ;
}
// connection accepted immediately
finishAccept();
// allow another accept before the result is set
enableAccept();
result.setResult(channel);
}
} finally {
// end usage on child socket
channel.end();
}
} catch (Throwable x) {
// failed to initiate accept so release resources
if (overlapped != 0L )
ioCache.remove(overlapped);
closeChildChannel();
if (x instanceof ClosedChannelException)
x = new AsynchronousCloseException();
if ( ! (x instanceof IOException) && ! (x instanceof SecurityException))
x = new IOException(x);
enableAccept();
result.setFailure(x);
} finally {
// end of usage of listener socket
schannel.end();
}
// accept completed immediately but may not have executed on
// initiating thread in which case the operation may have been
// cancelled.
if (result.isCancelled()) {
closeChildChannel();
}
// invoke completion handler
Invoker.invokeIndirectly(result);
}
/**
* Executed when the I/O has completed
*/
@Override
public void completed( int bytesTransferred, boolean canInvokeDirect) {
try {
// connection accept after group has shutdown
if (iocp.isShutdown()) {
throw new IOException( new ShutdownChannelGroupException());
}
// finish the accept
try {
schannel.begin();
try {
channel.begin();
finishAccept();
} finally {
channel.end();
}
} finally {
schannel.end();
}
// allow another accept before the result is set
enableAccept();
result.setResult(channel);
} catch (Throwable x) {
enableAccept();
closeChildChannel();
if (x instanceof ClosedChannelException)
x = new AsynchronousCloseException();
if ( ! (x instanceof IOException) && ! (x instanceof SecurityException))
x = new IOException(x);
result.setFailure(x);
}
// if an async cancel has already cancelled the operation then
// close the new channel so as to free resources
if (result.isCancelled()) {
closeChildChannel();
}
// invoke handler (but not directly)
Invoker.invokeIndirectly(result);
}
@Override
public void failed( int error, IOException x) {
enableAccept();
closeChildChannel();
// release waiters
if (schannel.isOpen()) {
result.setFailure(x);
} else {
result.setFailure( new AsynchronousCloseException());
}
Invoker.invokeIndirectly(result);
}
}
Future < AsynchronousSocketChannel > implAccept(Object attachment,
final CompletionHandler < AsynchronousSocketChannel, Object > handler) {
if ( ! schannel.isOpen()) {
Throwable exc = new ClosedChannelException();
if (handler == null )
return CompletedFuture.withFailure(exc);
Invoker.invokeIndirectly(schannel, handler, attachment, null , exc);
return null ;
}
if (schannel.isAcceptKilled())
throw new RuntimeException( " Accept not allowed due to cancellation " );
// ensure channel is bound to local address
if (schannel.localAddress == null )
throw new NotYetBoundException();
// create the socket that will be accepted. The creation of the socket
// is enclosed by a begin/end for the listener socket to ensure that
// we check that the listener is open and also to prevent the I/O
// port from being closed as the new socket is registered.
WindowsAsynchronousSocketChannelImpl ch = null ;
IOException ioe = null ;
try {
schannel.begin();
ch = new WindowsAsynchronousSocketChannelImpl(iocp, false );
} catch (IOException x) {
ioe = x;
} finally {
schannel.end();
}
if (ioe != null ) {
if (handler == null )
return CompletedFuture.withFailure(ioe);
Invoker.invokeIndirectly( this .schannel, handler, attachment, null , ioe);
return null ;
}
// need calling context when there is security manager as
// permission check may be done in a different thread without
// any application call frames on the stack
AccessControlContext acc =
(System.getSecurityManager() == null ) ? null : AccessController.getContext();
PendingFuture < AsynchronousSocketChannel, Object > result =
new PendingFuture < AsynchronousSocketChannel, Object > (schannel, handler, attachment);
// check and set flag to prevent concurrent accepting
if (pendingAccept.get() >= maxPending)
throw new AcceptPendingException();
pendingAccept.incrementAndGet();
AcceptTask task = new AcceptTask(ch, acc, dataBuffers.poll(), result);
result.setContext(task);
// initiate I/O
if (Iocp.supportsThreadAgnosticIo()) {
task.run();
} else {
Invoker.invokeOnThreadInThreadPool( this .schannel, task);
}
return result;
}
// // reimplements for performance
static native void updateAcceptContext( long listenSocket, long acceptSocket,
InetAddress[] addresses, int [] ports, long dataBuffer) throws IOException;
static native int accept0( long handle, long handle2, long overlapped, long dataBuffer);
}
*
*/
package sun.nio.ch;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.AcceptPendingException;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.CompletionHandler;
import java.nio.channels.NotYetBoundException;
import java.nio.channels.ShutdownChannelGroupException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import sun.misc.Unsafe;
/**
* This class enable multiple 'AcceptEx' post on the completion port, hence improve the concurrent connection number.
* @author Yvon
*
*/
public class WindowsMultiAcceptSupport {
WindowsAsynchronousServerSocketChannelImpl schannel;
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 2 * (sizeof(SOCKET_ADDRESS) + 16)
private static final int ONE_DATA_BUFFER_SIZE = 88 ;
private long handle;
private Iocp iocp;
// typically there will be zero, or one I/O operations pending. In rare
// cases there may be more. These rare cases arise when a sequence of accept
// operations complete immediately and handled by the initiating thread.
// The corresponding OVERLAPPED cannot be reused/released until the completion
// event has been posted.
private PendingIoCache ioCache;
private Queue < Long > dataBuffers;
// the data buffer to receive the local/remote socket address
// private final long dataBuffer;
private AtomicInteger pendingAccept;
private int maxPending;
Method updateAcceptContextM;
Method acceptM;
WindowsMultiAcceptSupport() {
// dummy for JNI code.
}
public void close() throws IOException {
schannel.close();
for ( int i = 0 ; i < maxPending + 1 ; i ++ ) // assert there is maxPending+1 buffer in the queue
{
long addr = dataBuffers.poll();
// release resources
unsafe.freeMemory(addr);
}
}
/**
*
*/
public WindowsMultiAcceptSupport(AsynchronousServerSocketChannel ch, int maxPost) {
if (maxPost <= 0 || maxPost > 1024 )
throw new IllegalStateException( " maxPost can't less than 1 and greater than 1024 " );
this .schannel = (WindowsAsynchronousServerSocketChannelImpl) ch;
maxPending = maxPost;
dataBuffers = new ConcurrentLinkedQueue < Long > ();
for ( int i = 0 ; i < maxPending + 1 ; i ++ ) {
dataBuffers.add(unsafe.allocateMemory(ONE_DATA_BUFFER_SIZE));
}
pendingAccept = new AtomicInteger( 0 );
try {
Field f = WindowsAsynchronousServerSocketChannelImpl. class .getDeclaredField( " handle " );
f.setAccessible( true );
handle = f.getLong(schannel);
f = WindowsAsynchronousServerSocketChannelImpl. class .getDeclaredField( " iocp " );
f.setAccessible( true );
iocp = (Iocp) f.get(schannel);
f = WindowsAsynchronousServerSocketChannelImpl. class .getDeclaredField( " ioCache " );
f.setAccessible( true );
ioCache = (PendingIoCache) f.get(schannel);
f = WindowsAsynchronousServerSocketChannelImpl. class .getDeclaredField( " accepting " );
f.setAccessible( true );
AtomicBoolean accepting = (AtomicBoolean) f.get(schannel);
accepting.set( true ); // disable accepting by origin channel.
} catch (Exception e) {
e.printStackTrace();
}
}
@SuppressWarnings( " unchecked " )
public final < A > void accept(A attachment,
CompletionHandler < AsynchronousSocketChannel, ? super A > handler) {
if (handler == null )
throw new NullPointerException( " 'handler' is null " );
implAccept(attachment, (CompletionHandler < AsynchronousSocketChannel, Object > ) handler);
}
/**
* Task to initiate accept operation and to handle result.
*/
private class AcceptTask implements Runnable, Iocp.ResultHandler {
private final WindowsAsynchronousSocketChannelImpl channel;
private final AccessControlContext acc;
private final PendingFuture < AsynchronousSocketChannel, Object > result;
private final long dataBuffer;
AcceptTask(WindowsAsynchronousSocketChannelImpl channel, AccessControlContext acc,
long dataBuffer, PendingFuture < AsynchronousSocketChannel, Object > result) {
this .channel = channel;
this .acc = acc;
this .result = result;
this .dataBuffer = dataBuffer;
}
void enableAccept() {
pendingAccept.decrementAndGet();
dataBuffers.add(dataBuffer);
}
void closeChildChannel() {
try {
channel.close();
} catch (IOException ignore) {
}
}
// caller must have acquired read lock for the listener and child channel.
void finishAccept() throws IOException {
/**
* JDK7 use 4 calls to getsockname to setup
* local& remote address, this is very inefficient.
*
* I change this to use GetAcceptExSockaddrs
*/
InetAddress[] socks = new InetAddress[ 2 ];
int [] ports = new int [ 2 ];
updateAcceptContext(handle, channel.handle(), socks, ports, dataBuffer);
InetSocketAddress local = new InetSocketAddress(socks[ 0 ], ports[ 0 ]);
final InetSocketAddress remote = new InetSocketAddress(socks[ 1 ], ports[ 1 ]);
channel.setConnected(local, remote);
// permission check (in context of initiating thread)
if (acc != null ) {
AccessController.doPrivileged( new PrivilegedAction < Void > () {
public Void run() {
SecurityManager sm = System.getSecurityManager();
sm.checkAccept(remote.getAddress().getHostAddress(), remote.getPort());
return null ;
}
}, acc);
}
}
/**
* Initiates the accept operation.
*/
@Override
public void run() {
long overlapped = 0L ;
try {
// begin usage of listener socket
schannel.begin();
try {
// begin usage of child socket (as it is registered with
// completion port and so may be closed in the event that
// the group is forcefully closed).
channel.begin();
synchronized (result) {
overlapped = ioCache.add(result);
int n = accept0(handle, channel.handle(), overlapped, dataBuffer); // Be careful for the buffer address
if (n == IOStatus.UNAVAILABLE) {
return ;
}
// connection accepted immediately
finishAccept();
// allow another accept before the result is set
enableAccept();
result.setResult(channel);
}
} finally {
// end usage on child socket
channel.end();
}
} catch (Throwable x) {
// failed to initiate accept so release resources
if (overlapped != 0L )
ioCache.remove(overlapped);
closeChildChannel();
if (x instanceof ClosedChannelException)
x = new AsynchronousCloseException();
if ( ! (x instanceof IOException) && ! (x instanceof SecurityException))
x = new IOException(x);
enableAccept();
result.setFailure(x);
} finally {
// end of usage of listener socket
schannel.end();
}
// accept completed immediately but may not have executed on
// initiating thread in which case the operation may have been
// cancelled.
if (result.isCancelled()) {
closeChildChannel();
}
// invoke completion handler
Invoker.invokeIndirectly(result);
}
/**
* Executed when the I/O has completed
*/
@Override
public void completed( int bytesTransferred, boolean canInvokeDirect) {
try {
// connection accept after group has shutdown
if (iocp.isShutdown()) {
throw new IOException( new ShutdownChannelGroupException());
}
// finish the accept
try {
schannel.begin();
try {
channel.begin();
finishAccept();
} finally {
channel.end();
}
} finally {
schannel.end();
}
// allow another accept before the result is set
enableAccept();
result.setResult(channel);
} catch (Throwable x) {
enableAccept();
closeChildChannel();
if (x instanceof ClosedChannelException)
x = new AsynchronousCloseException();
if ( ! (x instanceof IOException) && ! (x instanceof SecurityException))
x = new IOException(x);
result.setFailure(x);
}
// if an async cancel has already cancelled the operation then
// close the new channel so as to free resources
if (result.isCancelled()) {
closeChildChannel();
}
// invoke handler (but not directly)
Invoker.invokeIndirectly(result);
}
@Override
public void failed( int error, IOException x) {
enableAccept();
closeChildChannel();
// release waiters
if (schannel.isOpen()) {
result.setFailure(x);
} else {
result.setFailure( new AsynchronousCloseException());
}
Invoker.invokeIndirectly(result);
}
}
Future < AsynchronousSocketChannel > implAccept(Object attachment,
final CompletionHandler < AsynchronousSocketChannel, Object > handler) {
if ( ! schannel.isOpen()) {
Throwable exc = new ClosedChannelException();
if (handler == null )
return CompletedFuture.withFailure(exc);
Invoker.invokeIndirectly(schannel, handler, attachment, null , exc);
return null ;
}
if (schannel.isAcceptKilled())
throw new RuntimeException( " Accept not allowed due to cancellation " );
// ensure channel is bound to local address
if (schannel.localAddress == null )
throw new NotYetBoundException();
// create the socket that will be accepted. The creation of the socket
// is enclosed by a begin/end for the listener socket to ensure that
// we check that the listener is open and also to prevent the I/O
// port from being closed as the new socket is registered.
WindowsAsynchronousSocketChannelImpl ch = null ;
IOException ioe = null ;
try {
schannel.begin();
ch = new WindowsAsynchronousSocketChannelImpl(iocp, false );
} catch (IOException x) {
ioe = x;
} finally {
schannel.end();
}
if (ioe != null ) {
if (handler == null )
return CompletedFuture.withFailure(ioe);
Invoker.invokeIndirectly( this .schannel, handler, attachment, null , ioe);
return null ;
}
// need calling context when there is security manager as
// permission check may be done in a different thread without
// any application call frames on the stack
AccessControlContext acc =
(System.getSecurityManager() == null ) ? null : AccessController.getContext();
PendingFuture < AsynchronousSocketChannel, Object > result =
new PendingFuture < AsynchronousSocketChannel, Object > (schannel, handler, attachment);
// check and set flag to prevent concurrent accepting
if (pendingAccept.get() >= maxPending)
throw new AcceptPendingException();
pendingAccept.incrementAndGet();
AcceptTask task = new AcceptTask(ch, acc, dataBuffers.poll(), result);
result.setContext(task);
// initiate I/O
if (Iocp.supportsThreadAgnosticIo()) {
task.run();
} else {
Invoker.invokeOnThreadInThreadPool( this .schannel, task);
}
return result;
}
// // reimplements for performance
static native void updateAcceptContext( long listenSocket, long acceptSocket,
InetAddress[] addresses, int [] ports, long dataBuffer) throws IOException;
static native int accept0( long handle, long handle2, long overlapped, long dataBuffer);
}
对应的CPP代码如下:
/*
* Class: sun_nio_ch_WindowsMultiAcceptSupport
* Method: updateAcceptContext
* Signature: (JJ[Ljava/net/InetAddress;[IJ)V
*/
JNIEXPORT void JNICALL Java_sun_nio_ch_WindowsMultiAcceptSupport_updateAcceptContext
(JNIEnv * env , jclass clazz, jlong listenSocket, jlong acceptSocket, jobjectArray sockArray,jintArray portArray,jlong buf)
{
SOCKET s1 = (SOCKET)jlong_to_ptr(listenSocket);
SOCKET s2 = (SOCKET)jlong_to_ptr(acceptSocket);
PVOID outputBuffer = (PVOID)jlong_to_ptr(buf);
INT iLocalAddrLen = 0 ;
INT iRemoteAddrLen = 0 ;
SOCKETADDRESS * lpLocalAddr;
SOCKETADDRESS * lpRemoteAddr;
jobject localAddr;
jobject remoteAddr;
jint ports[ 2 ] = { 0 };
setsockopt(s2, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, ( char * ) & s1, sizeof (s1));
(lpGetAcceptExSockaddrs)(outputBuffer,
0 ,
sizeof (SOCKETADDRESS) + 16 ,
sizeof (SOCKETADDRESS) + 16 ,
(LPSOCKADDR * ) & lpLocalAddr,
& iLocalAddrLen,
(LPSOCKADDR * ) & lpRemoteAddr,
& iRemoteAddrLen);
localAddr = lpNET_SockaddrToInetAddress(env,( struct sockaddr * )lpLocalAddr,( int * )ports);
remoteAddr = lpNET_SockaddrToInetAddress(env,( struct sockaddr * )lpRemoteAddr,( int * )(ports + 1 ));
env -> SetObjectArrayElement(sockArray, 0 ,localAddr);
env -> SetObjectArrayElement(sockArray, 1 ,remoteAddr);
env -> SetIntArrayRegion(portArray, 0 , 2 ,ports);
}
/*
* Class: sun_nio_ch_WindowsMultiAcceptSupport
* Method: accept0
* Signature: (JJJJ)I
*/
jint JNICALL Java_sun_nio_ch_WindowsMultiAcceptSupport_accept0
(JNIEnv * env, jclass clazz, jlong listenSocket, jlong acceptSocket, jlong ov, jlong buf)
{
BOOL res;
SOCKET s1 = (SOCKET)jlong_to_ptr(listenSocket);
SOCKET s2 = (SOCKET)jlong_to_ptr(acceptSocket);
PVOID outputBuffer = (PVOID)jlong_to_ptr(buf);
DWORD nread = 0 ;
OVERLAPPED * lpOverlapped = (OVERLAPPED * )jlong_to_ptr(ov);
ZeroMemory((PVOID)lpOverlapped, sizeof (OVERLAPPED));
// why use SOCKETADDRESS?
// because client may use IPv6 to connect to server.
res = (lpAcceptEx)(s1,
s2,
outputBuffer,
0 ,
sizeof (SOCKETADDRESS) + 16 ,
sizeof (SOCKETADDRESS) + 16 ,
& nread,
lpOverlapped);
if (res == 0 ) {
int error = WSAGetLastError();
if (error == ERROR_IO_PENDING) {
return NIO2_IOS_UNAVAILABLE;
}
return NIO2_THROWN;
}
return 0 ;
}
这里用到的lpNET_SockaddrToInetAddress是JDK7中NET.DLL暴露的方法,从DLL里加载。相应代码如下:
*
* Class: com_yovn_jabhttpd_utilities_SunPackageFixer
* Method: initFds
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_yovn_jabhttpd_utilities_SunPackageFixer_initFds
(JNIEnv * env, jclass clazz)
{
GUID GuidAcceptEx = WSAID_ACCEPTEX;
GUID GuidTransmitFile = WSAID_TRANSMITFILE;
GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS;
SOCKET s;
int rv;
DWORD dwBytes;
HMODULE hModule;
s = socket(AF_INET, SOCK_STREAM, 0 );
if (s == INVALID_SOCKET) {
JNU_ThrowByName(env, " java/io/IOException " , " socket failed " );
return ;
}
rv = WSAIoctl(s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
(LPVOID) & GuidAcceptEx,
sizeof (GuidAcceptEx),
& lpAcceptEx,
sizeof (lpAcceptEx),
& dwBytes,
NULL,
NULL);
if (rv != 0 )
{
JNU_ThrowByName(env, " java/io/IOException " , " WSAIoctl failed on get AcceptEx " );
goto _ret;
}
rv = WSAIoctl(s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
(LPVOID) & GuidTransmitFile,
sizeof (GuidTransmitFile),
& lpTransmitFile,
sizeof (lpTransmitFile),
& dwBytes,
NULL,
NULL);
if (rv != 0 )
{
JNU_ThrowByName(env, " java/io/IOException " , " WSAIoctl failed on get TransmitFile " );
goto _ret;
}
rv = WSAIoctl(s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
(LPVOID) & GuidGetAcceptExSockAddrs,
sizeof (GuidGetAcceptExSockAddrs),
& lpGetAcceptExSockaddrs,
sizeof (lpGetAcceptExSockaddrs),
& dwBytes,
NULL,
NULL);
if (rv != 0 )
{
JNU_ThrowByName(env, " java/io/IOException " , " WSAIoctl failed on get GetAcceptExSockaddrs " );
goto _ret;
}
hModule = LoadLibrary( " net.dll " );
if (hModule == NULL)
{
JNU_ThrowByName(env, " java/io/IOException " , " can't load java net.dll " );
goto _ret;
}
lpNET_SockaddrToInetAddress = (NET_SockaddrToInetAddress_t)GetProcAddress(hModule, " _NET_SockaddrToInetAddress@12 " );
if (lpNET_SockaddrToInetAddress == NULL)
{
JNU_ThrowByName(env, " java/io/IOException " , " can't resolve _NET_SockaddrToInetAddress function " );
}
_ret:
closesocket(s);
return ;
}
* Class: com_yovn_jabhttpd_utilities_SunPackageFixer
* Method: initFds
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_yovn_jabhttpd_utilities_SunPackageFixer_initFds
(JNIEnv * env, jclass clazz)
{
GUID GuidAcceptEx = WSAID_ACCEPTEX;
GUID GuidTransmitFile = WSAID_TRANSMITFILE;
GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS;
SOCKET s;
int rv;
DWORD dwBytes;
HMODULE hModule;
s = socket(AF_INET, SOCK_STREAM, 0 );
if (s == INVALID_SOCKET) {
JNU_ThrowByName(env, " java/io/IOException " , " socket failed " );
return ;
}
rv = WSAIoctl(s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
(LPVOID) & GuidAcceptEx,
sizeof (GuidAcceptEx),
& lpAcceptEx,
sizeof (lpAcceptEx),
& dwBytes,
NULL,
NULL);
if (rv != 0 )
{
JNU_ThrowByName(env, " java/io/IOException " , " WSAIoctl failed on get AcceptEx " );
goto _ret;
}
rv = WSAIoctl(s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
(LPVOID) & GuidTransmitFile,
sizeof (GuidTransmitFile),
& lpTransmitFile,
sizeof (lpTransmitFile),
& dwBytes,
NULL,
NULL);
if (rv != 0 )
{
JNU_ThrowByName(env, " java/io/IOException " , " WSAIoctl failed on get TransmitFile " );
goto _ret;
}
rv = WSAIoctl(s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
(LPVOID) & GuidGetAcceptExSockAddrs,
sizeof (GuidGetAcceptExSockAddrs),
& lpGetAcceptExSockaddrs,
sizeof (lpGetAcceptExSockaddrs),
& dwBytes,
NULL,
NULL);
if (rv != 0 )
{
JNU_ThrowByName(env, " java/io/IOException " , " WSAIoctl failed on get GetAcceptExSockaddrs " );
goto _ret;
}
hModule = LoadLibrary( " net.dll " );
if (hModule == NULL)
{
JNU_ThrowByName(env, " java/io/IOException " , " can't load java net.dll " );
goto _ret;
}
lpNET_SockaddrToInetAddress = (NET_SockaddrToInetAddress_t)GetProcAddress(hModule, " _NET_SockaddrToInetAddress@12 " );
if (lpNET_SockaddrToInetAddress == NULL)
{
JNU_ThrowByName(env, " java/io/IOException " , " can't resolve _NET_SockaddrToInetAddress function " );
}
_ret:
closesocket(s);
return ;
}
细心的同学可能会发现,在创建socket之前没有初始化WinSock库,因为在这段代码前,我初始化了一个InetSocketAddress对象,这样JVM会加载NET.DLL并初始化WinSock库了。
OK,现在,你可以在支持类上同时发起多个AcceptEx请求了。
PS:基于这个我简单测试了下我的服务器,同时开5000个线程,每个下载3M多点的文件,一分钟内能够全部正确完成。
服务器正在开发中,有兴趣的请加入:http://code.google.com/p/jabhttpd