|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.nio.ch; |
|
|
|
import java.io.FileDescriptor; |
|
import java.io.IOException; |
|
import java.net.BindException; |
|
import java.net.InetSocketAddress; |
|
import java.net.ProtocolFamily; |
|
import java.net.ServerSocket; |
|
import java.net.SocketAddress; |
|
import java.net.SocketOption; |
|
import java.net.SocketTimeoutException; |
|
import java.net.StandardSocketOptions; |
|
import java.net.UnixDomainSocketAddress; |
|
import java.nio.channels.AlreadyBoundException; |
|
import java.nio.channels.AsynchronousCloseException; |
|
import java.nio.channels.ClosedChannelException; |
|
import java.nio.channels.IllegalBlockingModeException; |
|
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.nio.file.Path; |
|
import java.util.Collections; |
|
import java.util.HashSet; |
|
import java.util.Set; |
|
import java.util.Objects; |
|
import java.util.concurrent.locks.ReentrantLock; |
|
import static java.net.StandardProtocolFamily.INET; |
|
import static java.net.StandardProtocolFamily.INET6; |
|
import static java.net.StandardProtocolFamily.UNIX; |
|
|
|
import sun.net.NetHooks; |
|
import sun.net.ext.ExtendedSocketOptions; |
|
|
|
/** |
|
* An implementation of ServerSocketChannels |
|
*/ |
|
|
|
class ServerSocketChannelImpl |
|
extends ServerSocketChannel |
|
implements SelChImpl |
|
{ |
|
|
|
private static final NativeDispatcher nd = new SocketDispatcher(); |
|
|
|
|
|
private final ProtocolFamily family; |
|
|
|
|
|
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_CLOSED = 2; |
|
private int state; |
|
|
|
|
|
private long thread; |
|
|
|
// Binding |
|
private SocketAddress localAddress; |
|
|
|
|
|
private boolean isReuseAddress; |
|
|
|
|
|
private ServerSocket socket; |
|
|
|
// -- End of fields protected by stateLock |
|
|
|
ServerSocketChannelImpl(SelectorProvider sp) throws IOException { |
|
this(sp, Net.isIPv6Available() ? INET6 : INET); |
|
} |
|
|
|
ServerSocketChannelImpl(SelectorProvider sp, ProtocolFamily family) |
|
throws IOException |
|
{ |
|
super(sp); |
|
Objects.requireNonNull(family, "'family' is null"); |
|
if ((family != INET) && (family != INET6) && (family != UNIX)) { |
|
throw new UnsupportedOperationException("Protocol family not supported"); |
|
} |
|
if (family == INET6 && !Net.isIPv6Available()) { |
|
throw new UnsupportedOperationException("IPv6 not available"); |
|
} |
|
|
|
this.family = family; |
|
if (family == UNIX) { |
|
this.fd = UnixDomainSockets.socket(); |
|
} else { |
|
this.fd = Net.serverSocket(family, true); |
|
} |
|
this.fdVal = IOUtil.fdVal(fd); |
|
} |
|
|
|
ServerSocketChannelImpl(SelectorProvider sp, |
|
ProtocolFamily family, |
|
FileDescriptor fd, |
|
boolean bound) |
|
throws IOException |
|
{ |
|
super(sp); |
|
|
|
if (family == UNIX) { |
|
this.family = UNIX; |
|
} else { |
|
this.family = Net.isIPv6Available() ? INET6 : INET; |
|
} |
|
this.fd = fd; |
|
this.fdVal = IOUtil.fdVal(fd); |
|
|
|
if (bound) { |
|
synchronized (stateLock) { |
|
if (family == UNIX) { |
|
localAddress = UnixDomainSockets.localAddress(fd); |
|
} else { |
|
localAddress = Net.localAddress(fd); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private boolean isNetSocket() { |
|
return (family == INET) || (family == INET6); |
|
} |
|
|
|
|
|
|
|
*/ |
|
boolean isUnixSocket() { |
|
return (family == UNIX); |
|
} |
|
|
|
|
|
private void ensureOpen() throws ClosedChannelException { |
|
if (!isOpen()) |
|
throw new ClosedChannelException(); |
|
} |
|
|
|
@Override |
|
public ServerSocket socket() { |
|
synchronized (stateLock) { |
|
if (socket == null) { |
|
if (isNetSocket()) { |
|
socket = ServerSocketAdaptor.create(this); |
|
} else { |
|
throw new UnsupportedOperationException("Not supported"); |
|
} |
|
} |
|
return socket; |
|
} |
|
} |
|
|
|
@Override |
|
public SocketAddress getLocalAddress() throws IOException { |
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
if (isUnixSocket()) { |
|
return UnixDomainSockets.getRevealedLocalAddress(localAddress); |
|
} else { |
|
return 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"); |
|
if (!name.type().isInstance(value)) |
|
throw new IllegalArgumentException("Invalid value '" + value + "'"); |
|
|
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
if (isNetSocket() |
|
&& 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 (isNetSocket() |
|
&& name == StandardSocketOptions.SO_REUSEADDR |
|
&& Net.useExclusiveBind()) { |
|
|
|
return (T) Boolean.valueOf(isReuseAddress); |
|
} else { |
|
|
|
return (T) Net.getSocketOption(fd, Net.UNSPEC, name); |
|
} |
|
} |
|
} |
|
|
|
private static class DefaultOptionsHolder { |
|
static final Set<SocketOption<?>> defaultInetOptions = defaultInetOptions(); |
|
static final Set<SocketOption<?>> defaultUnixDomainOptions = defaultUnixDomainOptions(); |
|
|
|
private static Set<SocketOption<?>> defaultInetOptions() { |
|
HashSet<SocketOption<?>> set = new HashSet<>(); |
|
set.add(StandardSocketOptions.SO_RCVBUF); |
|
set.add(StandardSocketOptions.SO_REUSEADDR); |
|
if (Net.isReusePortAvailable()) { |
|
set.add(StandardSocketOptions.SO_REUSEPORT); |
|
} |
|
set.addAll(ExtendedSocketOptions.serverSocketOptions()); |
|
return Collections.unmodifiableSet(set); |
|
} |
|
|
|
private static Set<SocketOption<?>> defaultUnixDomainOptions() { |
|
HashSet<SocketOption<?>> set = new HashSet<>(); |
|
set.add(StandardSocketOptions.SO_RCVBUF); |
|
return Collections.unmodifiableSet(set); |
|
} |
|
} |
|
|
|
@Override |
|
public final Set<SocketOption<?>> supportedOptions() { |
|
if (isUnixSocket()) { |
|
return DefaultOptionsHolder.defaultUnixDomainOptions; |
|
} else { |
|
return DefaultOptionsHolder.defaultInetOptions; |
|
} |
|
} |
|
|
|
@Override |
|
public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException { |
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
if (localAddress != null) |
|
throw new AlreadyBoundException(); |
|
if (isUnixSocket()) { |
|
localAddress = unixBind(local, backlog); |
|
} else { |
|
localAddress = netBind(local, backlog); |
|
} |
|
} |
|
return this; |
|
} |
|
|
|
private SocketAddress unixBind(SocketAddress local, int backlog) throws IOException { |
|
UnixDomainSockets.checkPermission(); |
|
if (local == null) { |
|
// Attempt up to 10 times to find an unused name in temp directory. |
|
|
|
boolean bound = false; |
|
int attempts = 0; |
|
while (attempts < 10 && !bound) { |
|
try { |
|
Path path = UnixDomainSockets.generateTempName().getPath(); |
|
UnixDomainSockets.bind(fd, path); |
|
bound = true; |
|
} catch (BindException e) { } |
|
attempts++; |
|
} |
|
if (!bound) |
|
throw new BindException("Could not bind to temporary name"); |
|
} else { |
|
Path path = UnixDomainSockets.checkAddress(local).getPath(); |
|
UnixDomainSockets.bind(fd, path); |
|
} |
|
Net.listen(fd, backlog < 1 ? 50 : backlog); |
|
return UnixDomainSockets.localAddress(fd); |
|
} |
|
|
|
private SocketAddress netBind(SocketAddress local, int backlog) throws IOException { |
|
InetSocketAddress isa; |
|
if (local == null) { |
|
isa = new InetSocketAddress(Net.anyLocalAddress(family), 0); |
|
} else { |
|
isa = Net.checkAddress(local, family); |
|
} |
|
@SuppressWarnings("removal") |
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm != null) |
|
sm.checkListen(isa.getPort()); |
|
NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort()); |
|
Net.bind(family, fd, isa.getAddress(), isa.getPort()); |
|
Net.listen(fd, backlog < 1 ? 50 : backlog); |
|
return Net.localAddress(fd); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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) { |
|
tryFinishClose(); |
|
} |
|
} |
|
end(completed); |
|
} |
|
} |
|
|
|
@Override |
|
public SocketChannel accept() throws IOException { |
|
int n = 0; |
|
FileDescriptor newfd = new FileDescriptor(); |
|
SocketAddress[] saa = new SocketAddress[1]; |
|
|
|
acceptLock.lock(); |
|
try { |
|
boolean blocking = isBlocking(); |
|
try { |
|
begin(blocking); |
|
n = implAccept(this.fd, newfd, saa); |
|
if (blocking) { |
|
while (IOStatus.okayToRetry(n) && isOpen()) { |
|
park(Net.POLLIN); |
|
n = implAccept(this.fd, newfd, saa); |
|
} |
|
} |
|
} finally { |
|
end(blocking, n > 0); |
|
assert IOStatus.check(n); |
|
} |
|
} finally { |
|
acceptLock.unlock(); |
|
} |
|
|
|
if (n > 0) { |
|
return finishAccept(newfd, saa[0]); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
private int implAccept(FileDescriptor fd, FileDescriptor newfd, SocketAddress[] saa) |
|
throws IOException |
|
{ |
|
if (isUnixSocket()) { |
|
UnixDomainSockets.checkPermission(); |
|
String[] pa = new String[1]; |
|
int n = UnixDomainSockets.accept(fd, newfd, pa); |
|
if (n > 0) |
|
saa[0] = UnixDomainSocketAddress.of(pa[0]); |
|
return n; |
|
} else { |
|
InetSocketAddress[] issa = new InetSocketAddress[1]; |
|
int n = Net.accept(fd, newfd, issa); |
|
if (n > 0) |
|
saa[0] = issa[0]; |
|
return n; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
SocketChannel blockingAccept(long nanos) throws IOException { |
|
int n = 0; |
|
FileDescriptor newfd = new FileDescriptor(); |
|
SocketAddress[] saa = new SocketAddress[1]; |
|
|
|
acceptLock.lock(); |
|
try { |
|
|
|
if (!isBlocking()) |
|
throw new IllegalBlockingModeException(); |
|
|
|
try { |
|
begin(true); |
|
|
|
lockedConfigureBlocking(false); |
|
try { |
|
long startNanos = System.nanoTime(); |
|
n = implAccept(fd, newfd, saa); |
|
while (n == IOStatus.UNAVAILABLE && isOpen()) { |
|
long remainingNanos = nanos - (System.nanoTime() - startNanos); |
|
if (remainingNanos <= 0) { |
|
throw new SocketTimeoutException("Accept timed out"); |
|
} |
|
park(Net.POLLIN, remainingNanos); |
|
n = implAccept(fd, newfd, saa); |
|
} |
|
} finally { |
|
|
|
tryLockedConfigureBlocking(true); |
|
} |
|
} finally { |
|
end(true, n > 0); |
|
} |
|
} finally { |
|
acceptLock.unlock(); |
|
} |
|
|
|
assert n > 0; |
|
return finishAccept(newfd, saa[0]); |
|
} |
|
|
|
private SocketChannel finishAccept(FileDescriptor newfd, SocketAddress sa) |
|
throws IOException |
|
{ |
|
try { |
|
|
|
IOUtil.configureBlocking(newfd, true); |
|
|
|
|
|
if (isNetSocket()) { |
|
@SuppressWarnings("removal") |
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm != null) { |
|
InetSocketAddress isa = (InetSocketAddress) sa; |
|
sm.checkAccept(isa.getAddress().getHostAddress(), isa.getPort()); |
|
} |
|
} |
|
return new SocketChannelImpl(provider(), family, newfd, sa); |
|
} catch (Exception e) { |
|
nd.close(newfd); |
|
throw e; |
|
} |
|
} |
|
|
|
@Override |
|
protected void implConfigureBlocking(boolean block) throws IOException { |
|
acceptLock.lock(); |
|
try { |
|
lockedConfigureBlocking(block); |
|
} finally { |
|
acceptLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void lockedConfigureBlocking(boolean block) throws IOException { |
|
assert acceptLock.isHeldByCurrentThread(); |
|
synchronized (stateLock) { |
|
ensureOpen(); |
|
IOUtil.configureBlocking(fd, block); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private boolean tryLockedConfigureBlocking(boolean block) throws IOException { |
|
assert acceptLock.isHeldByCurrentThread(); |
|
synchronized (stateLock) { |
|
if (isOpen()) { |
|
IOUtil.configureBlocking(fd, block); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private boolean tryClose() throws IOException { |
|
assert Thread.holdsLock(stateLock) && state == ST_CLOSING; |
|
if ((thread == 0) && !isRegistered()) { |
|
state = ST_CLOSED; |
|
nd.close(fd); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void tryFinishClose() { |
|
try { |
|
tryClose(); |
|
} catch (IOException ignore) { } |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void implCloseBlockingMode() throws IOException { |
|
synchronized (stateLock) { |
|
assert state < ST_CLOSING; |
|
state = ST_CLOSING; |
|
if (!tryClose()) { |
|
long th = thread; |
|
if (th != 0) { |
|
nd.preClose(fd); |
|
NativeThread.signal(th); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void implCloseNonBlockingMode() throws IOException { |
|
synchronized (stateLock) { |
|
assert state < ST_CLOSING; |
|
state = ST_CLOSING; |
|
} |
|
|
|
acceptLock.lock(); |
|
acceptLock.unlock(); |
|
synchronized (stateLock) { |
|
if (state == ST_CLOSING) { |
|
tryClose(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected void implCloseSelectableChannel() throws IOException { |
|
assert !isOpen(); |
|
if (isBlocking()) { |
|
implCloseBlockingMode(); |
|
} else { |
|
implCloseNonBlockingMode(); |
|
} |
|
} |
|
|
|
@Override |
|
public void kill() { |
|
synchronized (stateLock) { |
|
if (state == ST_CLOSING) { |
|
tryFinishClose(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
boolean isBound() { |
|
synchronized (stateLock) { |
|
return localAddress != null; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
SocketAddress localAddress() { |
|
synchronized (stateLock) { |
|
return localAddress; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
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) { |
|
SocketAddress addr = localAddress; |
|
if (addr == null) { |
|
sb.append("unbound"); |
|
} else if (isUnixSocket()) { |
|
sb.append(UnixDomainSockets.getRevealedLocalAddressAsString(addr)); |
|
} else { |
|
sb.append(Net.getRevealedLocalAddressAsString(addr)); |
|
} |
|
} |
|
} |
|
sb.append(']'); |
|
return sb.toString(); |
|
} |
|
} |