|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.nio.ch; |
|
|
|
import java.io.FileDescriptor; |
|
import java.io.IOException; |
|
import java.net.InetSocketAddress; |
|
import java.net.ProtocolFamily; |
|
import java.net.ServerSocket; |
|
import java.net.SocketAddress; |
|
import java.net.SocketOption; |
|
import java.net.StandardProtocolFamily; |
|
import java.net.StandardSocketOptions; |
|
import java.nio.channels.AlreadyBoundException; |
|
import java.nio.channels.AsynchronousCloseException; |
|
import java.nio.channels.ClosedChannelException; |
|
import java.nio.channels.NotYetBoundException; |
|
import java.nio.channels.SelectionKey; |
|
import java.nio.channels.ServerSocketChannel; |
|
import java.nio.channels.SocketChannel; |
|
import java.nio.channels.spi.SelectorProvider; |
|
import java.util.Collections; |
|
import java.util.HashSet; |
|
import java.util.Objects; |
|
import java.util.Set; |
|
import java.util.concurrent.locks.ReentrantLock; |
|
|
|
import sun.net.NetHooks; |
|
import sun.net.ext.ExtendedSocketOptions; |
|
import static sun.net.ext.ExtendedSocketOptions.SOCK_STREAM; |
|
|
|
/** |
|
* An implementation of ServerSocketChannels |
|
*/ |
|
|
|
class ServerSocketChannelImpl |
|
extends ServerSocketChannel |
|
implements SelChImpl |
|
{ |
|
|
|
private static NativeDispatcher nd; |
|
|
|
|
|
private final FileDescriptor fd; |
|
private final int fdVal; |
|
|
|
|
|
private final ReentrantLock acceptLock = new ReentrantLock(); |
|
|
|
// Lock held by any thread that modifies the state fields declared below |
|
|
|
private final Object stateLock = new Object(); |
|
|
|
// -- The following fields are protected by stateLock |
|
|
|
|
|
private static final int ST_INUSE = 0; |
|
private static final int ST_CLOSING = 1; |
|
private static final int ST_KILLPENDING = 2; |
|
private static final int ST_KILLED = 3; |
|
private int state; |
|
|
|
|
|
private long thread; |
|
|
|
// Binding |
|
private InetSocketAddress localAddress; |
|
|
|
|
|
private boolean isReuseAddress; |
|
|
|
|
|
private ServerSocket socket; |
|
|
|
// -- End of fields protected by stateLock |
|
|
|
|
|
ServerSocketChannelImpl(SelectorProvider sp) throws IOException { |
|
super(sp); |
|
this.fd = Net.serverSocket(true); |
|
this.fdVal = IOUtil.fdVal(fd); |
|
} |
|
|
|
ServerSocketChannelImpl(SelectorProvider sp, FileDescriptor fd, boolean bound) |
|
throws IOException |
|
{ |
|
super(sp); |
|
this.fd = fd; |
|
this.fdVal = IOUtil.fdVal(fd); |
|
if (bound) { |
|
synchronized (stateLock) { |
|
localAddress = Net.localAddress(fd); |
|
} |
|
} |
|
} |
|
|
|
|
|
private void ensureOpen() throws ClosedChannelException { |
|
if (!isOpen()) |
|
throw new ClosedChannelException(); |
|
} |
|
|
|
@Override |
|
public ServerSocket socket() { |
|
synchronized (stateLock) { |
|
if (socket == null) |
|
socket = ServerSocketAdaptor.create(this); |
|
return socket; |
|
} |
|
} |
|
|
|
@Override |
|
public SocketAddress getLocalAddress() throws IOException { |
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
return (localAddress == null) |
|
? null |
|
: Net.getRevealedLocalAddress(localAddress); |
|
} |
|
} |
|
|
|
@Override |
|
public <T> ServerSocketChannel setOption(SocketOption<T> name, T value) |
|
throws IOException |
|
{ |
|
Objects.requireNonNull(name); |
|
if (!supportedOptions().contains(name)) |
|
throw new UnsupportedOperationException("'" + name + "' not supported"); |
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
|
|
if (name == StandardSocketOptions.IP_TOS) { |
|
ProtocolFamily family = Net.isIPv6Available() ? |
|
StandardProtocolFamily.INET6 : StandardProtocolFamily.INET; |
|
Net.setSocketOption(fd, family, name, value); |
|
return this; |
|
} |
|
|
|
if (name == StandardSocketOptions.SO_REUSEADDR && Net.useExclusiveBind()) { |
|
|
|
isReuseAddress = (Boolean)value; |
|
} else { |
|
|
|
Net.setSocketOption(fd, Net.UNSPEC, name, value); |
|
} |
|
return this; |
|
} |
|
} |
|
|
|
@Override |
|
@SuppressWarnings("unchecked") |
|
public <T> T getOption(SocketOption<T> name) |
|
throws IOException |
|
{ |
|
Objects.requireNonNull(name); |
|
if (!supportedOptions().contains(name)) |
|
throw new UnsupportedOperationException("'" + name + "' not supported"); |
|
|
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
if (name == StandardSocketOptions.SO_REUSEADDR && Net.useExclusiveBind()) { |
|
|
|
return (T)Boolean.valueOf(isReuseAddress); |
|
} |
|
|
|
return (T) Net.getSocketOption(fd, Net.UNSPEC, name); |
|
} |
|
} |
|
|
|
private static class DefaultOptionsHolder { |
|
static final Set<SocketOption<?>> defaultOptions = defaultOptions(); |
|
|
|
private static Set<SocketOption<?>> defaultOptions() { |
|
HashSet<SocketOption<?>> set = new HashSet<>(); |
|
set.add(StandardSocketOptions.SO_RCVBUF); |
|
set.add(StandardSocketOptions.SO_REUSEADDR); |
|
if (Net.isReusePortAvailable()) { |
|
set.add(StandardSocketOptions.SO_REUSEPORT); |
|
} |
|
set.add(StandardSocketOptions.IP_TOS); |
|
set.addAll(ExtendedSocketOptions.options(SOCK_STREAM)); |
|
return Collections.unmodifiableSet(set); |
|
} |
|
} |
|
|
|
@Override |
|
public final Set<SocketOption<?>> supportedOptions() { |
|
return DefaultOptionsHolder.defaultOptions; |
|
} |
|
|
|
@Override |
|
public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException { |
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
if (localAddress != null) |
|
throw new AlreadyBoundException(); |
|
InetSocketAddress isa = (local == null) |
|
? new InetSocketAddress(0) |
|
: Net.checkAddress(local); |
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm != null) |
|
sm.checkListen(isa.getPort()); |
|
NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort()); |
|
Net.bind(fd, isa.getAddress(), isa.getPort()); |
|
Net.listen(fd, backlog < 1 ? 50 : backlog); |
|
localAddress = Net.localAddress(fd); |
|
} |
|
return this; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void begin(boolean blocking) throws ClosedChannelException { |
|
if (blocking) |
|
begin(); |
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
if (localAddress == null) |
|
throw new NotYetBoundException(); |
|
if (blocking) |
|
thread = NativeThread.current(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void end(boolean blocking, boolean completed) |
|
throws AsynchronousCloseException |
|
{ |
|
if (blocking) { |
|
synchronized (stateLock) { |
|
thread = 0; |
|
|
|
if (state == ST_CLOSING) { |
|
stateLock.notifyAll(); |
|
} |
|
} |
|
end(completed); |
|
} |
|
} |
|
|
|
@Override |
|
public SocketChannel accept() throws IOException { |
|
acceptLock.lock(); |
|
try { |
|
int n = 0; |
|
FileDescriptor newfd = new FileDescriptor(); |
|
InetSocketAddress[] isaa = new InetSocketAddress[1]; |
|
|
|
boolean blocking = isBlocking(); |
|
try { |
|
begin(blocking); |
|
do { |
|
n = accept(this.fd, newfd, isaa); |
|
} while (n == IOStatus.INTERRUPTED && isOpen()); |
|
} finally { |
|
end(blocking, n > 0); |
|
assert IOStatus.check(n); |
|
} |
|
|
|
if (n < 1) |
|
return null; |
|
|
|
|
|
IOUtil.configureBlocking(newfd, true); |
|
|
|
InetSocketAddress isa = isaa[0]; |
|
SocketChannel sc = new SocketChannelImpl(provider(), newfd, isa); |
|
|
|
|
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm != null) { |
|
try { |
|
sm.checkAccept(isa.getAddress().getHostAddress(), isa.getPort()); |
|
} catch (SecurityException x) { |
|
sc.close(); |
|
throw x; |
|
} |
|
} |
|
return sc; |
|
|
|
} finally { |
|
acceptLock.unlock(); |
|
} |
|
} |
|
|
|
@Override |
|
protected void implConfigureBlocking(boolean block) throws IOException { |
|
acceptLock.lock(); |
|
try { |
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
IOUtil.configureBlocking(fd, block); |
|
} |
|
} finally { |
|
acceptLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected void implCloseSelectableChannel() throws IOException { |
|
assert !isOpen(); |
|
|
|
boolean interrupted = false; |
|
boolean blocking; |
|
|
|
|
|
synchronized (stateLock) { |
|
assert state < ST_CLOSING; |
|
state = ST_CLOSING; |
|
blocking = isBlocking(); |
|
} |
|
|
|
|
|
if (blocking) { |
|
synchronized (stateLock) { |
|
assert state == ST_CLOSING; |
|
long th = thread; |
|
if (th != 0) { |
|
nd.preClose(fd); |
|
NativeThread.signal(th); |
|
|
|
|
|
while (thread != 0) { |
|
try { |
|
stateLock.wait(); |
|
} catch (InterruptedException e) { |
|
interrupted = true; |
|
} |
|
} |
|
} |
|
} |
|
} else { |
|
|
|
acceptLock.lock(); |
|
acceptLock.unlock(); |
|
} |
|
|
|
|
|
synchronized (stateLock) { |
|
assert state == ST_CLOSING; |
|
state = ST_KILLPENDING; |
|
} |
|
|
|
|
|
if (!isRegistered()) |
|
kill(); |
|
|
|
|
|
if (interrupted) |
|
Thread.currentThread().interrupt(); |
|
} |
|
|
|
@Override |
|
public void kill() throws IOException { |
|
synchronized (stateLock) { |
|
if (state == ST_KILLPENDING) { |
|
state = ST_KILLED; |
|
nd.close(fd); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
boolean isBound() { |
|
synchronized (stateLock) { |
|
return localAddress != null; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
InetSocketAddress localAddress() { |
|
synchronized (stateLock) { |
|
return localAddress; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
boolean pollAccept(long timeout) throws IOException { |
|
assert Thread.holdsLock(blockingLock()) && isBlocking(); |
|
acceptLock.lock(); |
|
try { |
|
boolean polled = false; |
|
try { |
|
begin(true); |
|
int events = Net.poll(fd, Net.POLLIN, timeout); |
|
polled = (events != 0); |
|
} finally { |
|
end(true, polled); |
|
} |
|
return polled; |
|
} finally { |
|
acceptLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public boolean translateReadyOps(int ops, int initialOps, SelectionKeyImpl ski) { |
|
int intOps = ski.nioInterestOps(); |
|
int oldOps = ski.nioReadyOps(); |
|
int newOps = initialOps; |
|
|
|
if ((ops & Net.POLLNVAL) != 0) { |
|
// This should only happen if this channel is pre-closed while a |
|
// selection operation is in progress |
|
|
|
return false; |
|
} |
|
|
|
if ((ops & (Net.POLLERR | Net.POLLHUP)) != 0) { |
|
newOps = intOps; |
|
ski.nioReadyOps(newOps); |
|
return (newOps & ~oldOps) != 0; |
|
} |
|
|
|
if (((ops & Net.POLLIN) != 0) && |
|
((intOps & SelectionKey.OP_ACCEPT) != 0)) |
|
newOps |= SelectionKey.OP_ACCEPT; |
|
|
|
ski.nioReadyOps(newOps); |
|
return (newOps & ~oldOps) != 0; |
|
} |
|
|
|
public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl ski) { |
|
return translateReadyOps(ops, ski.nioReadyOps(), ski); |
|
} |
|
|
|
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl ski) { |
|
return translateReadyOps(ops, 0, ski); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public int translateInterestOps(int ops) { |
|
int newOps = 0; |
|
if ((ops & SelectionKey.OP_ACCEPT) != 0) |
|
newOps |= Net.POLLIN; |
|
return newOps; |
|
} |
|
|
|
public FileDescriptor getFD() { |
|
return fd; |
|
} |
|
|
|
public int getFDVal() { |
|
return fdVal; |
|
} |
|
|
|
public String toString() { |
|
StringBuilder sb = new StringBuilder(); |
|
sb.append(this.getClass().getName()); |
|
sb.append('['); |
|
if (!isOpen()) { |
|
sb.append("closed"); |
|
} else { |
|
synchronized (stateLock) { |
|
InetSocketAddress addr = localAddress; |
|
if (addr == null) { |
|
sb.append("unbound"); |
|
} else { |
|
sb.append(Net.getRevealedLocalAddressAsString(addr)); |
|
} |
|
} |
|
} |
|
sb.append(']'); |
|
return sb.toString(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private int accept(FileDescriptor ssfd, |
|
FileDescriptor newfd, |
|
InetSocketAddress[] isaa) |
|
throws IOException |
|
{ |
|
return accept0(ssfd, newfd, isaa); |
|
} |
|
|
|
// -- Native methods -- |
|
|
|
// Accepts a new connection, setting the given file descriptor to refer to |
|
// the new socket and setting isaa[0] to the socket's remote address. |
|
// Returns 1 on success, or IOStatus.UNAVAILABLE (if non-blocking and no |
|
// connections are pending) or IOStatus.INTERRUPTED. |
|
|
|
private native int accept0(FileDescriptor ssfd, |
|
FileDescriptor newfd, |
|
InetSocketAddress[] isaa) |
|
throws IOException; |
|
|
|
private static native void initIDs(); |
|
|
|
static { |
|
IOUtil.load(); |
|
initIDs(); |
|
nd = new SocketDispatcher(); |
|
} |
|
|
|
} |