| // Copyright 2014 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.lib.unix; |
| |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.io.InterruptedIOException; |
| import java.net.Socket; |
| import java.net.SocketException; |
| import java.net.SocketTimeoutException; |
| |
| /** |
| * <p>An implementation of ServerSocket for local (AF_UNIX) sockets. |
| * |
| * <p>This class intentionally doesn't extend java.net.ServerSocket although it |
| * has some similarity to it. The java.net class hierarchy is a terrible mess |
| * and is inextricably coupled to the Internet Protocol. |
| * |
| * <p>This code is not intended to be portable to non-UNIX platforms. |
| */ |
| public class LocalServerSocket extends LocalSocket { |
| |
| // Socket timeout in milliseconds. No timeout by default. |
| private long soTimeoutMillis = 0; |
| |
| /** |
| * Constructs an unbound local server socket. |
| */ |
| public LocalServerSocket() throws IOException { |
| super(); |
| } |
| |
| /** |
| * Constructs a server socket, binds it to the specified address, and |
| * listens for incoming connections with the specified backlog. |
| * |
| * @throws IOException if any of the socket/bind/listen operations failed. |
| */ |
| public LocalServerSocket(LocalSocketAddress address, int backlog) |
| throws IOException { |
| this(); |
| bind(address); |
| listen(backlog); |
| } |
| |
| /** |
| * Constructs a server socket, binds it to the specified address, and begin |
| * listening for incoming connections using the default backlog. |
| * |
| * @throws IOException if any of the socket/bind/listen operations failed. |
| */ |
| public LocalServerSocket(LocalSocketAddress address) throws IOException { |
| this(address, 50); |
| } |
| |
| /** |
| * Specifies the timeout in milliseconds for accept(). Setting it to |
| * zero means an indefinite timeout. |
| */ |
| public void setSoTimeout(long timeoutMillis) { |
| soTimeoutMillis = timeoutMillis; |
| } |
| |
| /** |
| * Returns the current timeout in milliseconds. |
| */ |
| public long getSoTimeout() { |
| return soTimeoutMillis; |
| } |
| |
| /** |
| * Binds the specified address to this socket. The socket must be unbound. |
| * This causes the filesystem entry to appear. |
| * |
| * @throws IOException if the bind failed. |
| */ |
| public synchronized void bind(LocalSocketAddress address) |
| throws IOException { |
| if (address == null) { |
| throw new NullPointerException("address"); |
| } |
| checkNotClosed(); |
| if (state != State.NEW) { |
| throw new SocketException("socket is already bound to an address"); |
| } |
| bind(fd, address.getName().toString()); // JNI |
| this.address = address; |
| this.state = State.BOUND; |
| } |
| |
| /** |
| * Listen for incoming connections on a socket using the specfied backlog. |
| * The socket must be bound but not already listening. |
| * |
| * @throws IOException if the listen failed. |
| */ |
| public synchronized void listen(int backlog) throws IOException { |
| if (backlog < 1) { |
| throw new IllegalArgumentException("backlog=" + backlog); |
| } |
| checkNotClosed(); |
| if (address == null) { |
| throw new SocketException("socket has no address bound"); |
| } |
| if (state == State.LISTENING) { |
| throw new SocketException("socket is already listening"); |
| } |
| listen(fd, backlog); // JNI |
| this.state = State.LISTENING; |
| } |
| |
| /** |
| * Blocks until a connection is made to this socket and accepts it, returning |
| * a new socket connected to the client. |
| * |
| * @return the new socket connected to the client. |
| * @throws IOException if an error occurs when waiting for a connection. |
| * @throws SocketTimeoutException if a timeout was previously set with |
| * setSoTimeout and the timeout has been reached. |
| * @throws InterruptedIOException if the thread is interrupted when the |
| * method is blocked. |
| */ |
| public synchronized Socket accept() |
| throws IOException, SocketTimeoutException, InterruptedIOException { |
| if (state != State.LISTENING) { |
| throw new SocketException("socket is not in listening state"); |
| } |
| |
| // Throws a SocketTimeoutException if timeout. |
| if (soTimeoutMillis != 0) { |
| poll(fd, soTimeoutMillis); // JNI |
| } |
| |
| FileDescriptor clientFd = new FileDescriptor(); |
| accept(fd, clientFd); // JNI |
| final LocalSocketImpl impl = new LocalSocketImpl(clientFd); |
| return new Socket(impl) { |
| @Override |
| public boolean isConnected() { |
| return true; |
| } |
| @Override |
| public synchronized void close() throws IOException { |
| if (isClosed()) { |
| return; |
| } else { |
| super.close(); |
| // Workaround for the fact that super.created==false because we |
| // created the impl ourselves. As a result, super.close() doesn't |
| // call impl.close(). *Sigh*, java.net is horrendous. |
| // (Perhaps we should dispense with Socket/SocketImpl altogether?) |
| impl.close(); |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public String toString() { |
| return "LocalServerSocket(" + address + ")"; |
| } |
| } |