| /* |
| * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package nsk.share.jpda; |
| |
| import java.io.IOException; |
| import java.net.InetSocketAddress; |
| import java.net.ServerSocket; |
| import nsk.share.*; |
| |
| /* |
| * This class represents communication channel based on TCP/IP sockets. |
| * Usage of this class implies creation of objects of 2 types: server SocketIOPipe object |
| * (this object creates server socket and waits for incoming connection) and client |
| * SocketIOPipe (this object attaches to server). |
| * |
| * Server and client objects should be created using special static methods provided by this class, |
| * for example 'createServerIOPipe(Log log, int port, long timeout)' for server SocketIOPipe |
| * and 'createClientIOPipe(Log log, String host, int port, long timeout)' for client SocketIOPipe. |
| * |
| * When SocketIOPipe is created it can be used to send and receive strings using methods 'readln()' and 'println(String s)'. |
| * TCP/IP connection is established at the first attempt to read or write data. |
| * |
| * For example, if client process should send string 'OK' to the server process which is run |
| * at the host 'SERVER_HOST' following code can be written: |
| * |
| * Server side: |
| * |
| * // SocketIOPipe creates ServerSocket listening given port |
| * SocketIOPipe pipe = SocketIOPipe.createServerIOPipe(log, port, timeoutValue); |
| * |
| * // SocketIOPipe waits connection from client and reads data sent by the client |
| * String command = pipe.readln(); |
| * |
| * Client side: |
| * |
| * // initialize SocketIOPipe with given values of server host name and port |
| * SocketIOPipe pipe = SocketIOPipe.createClientIOPipe(log, 'SERVER_HOST', port, timeoutValue); |
| * |
| * String command = "OK"; |
| * // SocketIOPipe tries to create socket and send command to the server |
| * pipe.println(command); |
| * |
| */ |
| public class SocketIOPipe extends Log.Logger implements Finalizable { |
| |
| public static final int DEFAULT_TIMEOUT_VALUE = 1 * 60 * 1000; |
| |
| public static final String DEFAULT_PIPE_LOG_PREFIX = "SocketIOPipe> "; |
| |
| protected boolean listening; |
| |
| protected String host; |
| |
| protected int port; |
| |
| protected long timeout; |
| |
| protected SocketConnection connection; |
| |
| protected volatile boolean shouldStop; |
| |
| protected Process connectingProcess; |
| |
| protected ServerSocket serverSocket; |
| |
| protected String name; |
| |
| /** |
| * Make general <code>IOPipe</code> object with specified parameters. |
| */ |
| protected SocketIOPipe(String name, Log log, String logPrefix, String host, int port, long timeout, boolean listening) { |
| super(log, logPrefix); |
| this.host = host; |
| this.port = port; |
| this.timeout = timeout; |
| this.listening = listening; |
| this.name = name; |
| } |
| |
| /** |
| * Make general <code>IOPipe</code> object with specified parameters. |
| */ |
| protected SocketIOPipe(Log log, String logPrefix, String host, int port, long timeout, boolean listening) { |
| super(log, logPrefix); |
| this.host = host; |
| this.port = port; |
| this.timeout = timeout; |
| this.listening = listening; |
| } |
| |
| /** |
| * Create listening SocketIOPipe using given port |
| */ |
| public static SocketIOPipe createServerIOPipe(Log log, int port, long timeout) { |
| SocketIOPipe pipe = new SocketIOPipe(log, DEFAULT_PIPE_LOG_PREFIX, null, 0, timeout, true); |
| |
| try { |
| ServerSocket ss = new ServerSocket(); |
| if (port == 0) { |
| // Only need SO_REUSEADDR if we're using a fixed port. If we |
| // start seeing EADDRINUSE due to collisions in free ports |
| // then we should retry the bind() a few times. |
| ss.setReuseAddress(false); |
| } |
| ss.bind(new InetSocketAddress(port)); |
| pipe.setServerSocket(ss); |
| } catch (IOException e) { |
| e.printStackTrace(log.getOutStream()); |
| throw new Failure("Caught IOException while binding for IOPipe connection: \n\t" + e); |
| } |
| |
| return pipe; |
| } |
| |
| /** |
| * Create listening SocketIOPipe using any free port |
| */ |
| public static SocketIOPipe createServerIOPipe(Log log, long timeout) { |
| return createServerIOPipe(log, 0, timeout); |
| } |
| |
| /** |
| * Create attaching SocketIOPipe using given port and timeout |
| */ |
| public static SocketIOPipe createClientIOPipe(Log log, String host, int port, long timeout) { |
| return new SocketIOPipe(log, DEFAULT_PIPE_LOG_PREFIX, host, port, timeout, false); |
| } |
| |
| /** |
| * Return true if <code>IOPipe</code> connection established. |
| */ |
| public boolean isConnected() { |
| return (connection != null && connection.isConnected()); |
| } |
| |
| /** |
| * Returns port number used by SocketIOPipe |
| */ |
| public int getPort() { |
| return port; |
| } |
| |
| protected void setConnectingProcess(Process connectingProcess) { |
| this.connectingProcess = connectingProcess; |
| } |
| |
| protected void setServerSocket(ServerSocket serverSocket) { |
| this.serverSocket = serverSocket; |
| if (serverSocket != null) |
| port = serverSocket.getLocalPort(); |
| } |
| |
| /** |
| * Write (and flush) given <code>line</code> to this |
| * <code>IOPipe</code> cnannel. |
| * |
| * @throws Failure if error occured while sending data |
| */ |
| public void println(String line) { |
| if (connection == null) { |
| connect(); |
| } |
| connection.writeObject(line); |
| } |
| |
| /** |
| * Read a text line from this <code>IOPipe</code> channel, |
| * or return <i>null</i> if EOF reached. |
| * |
| * @throws Failure if error occured while reading data |
| */ |
| public String readln() { |
| if (connection == null) { |
| connect(); |
| } |
| String line = (String) connection.readObject(); |
| return line; |
| } |
| |
| /** |
| * Close this <code>IOPipe</code> connection. |
| */ |
| public void close() { |
| shouldStop = true; |
| if (connection != null) { |
| connection.close(); |
| } |
| } |
| |
| /** |
| * Establish <code>IOPipe</code> connection by attaching or accepting |
| * connection appropriately. |
| */ |
| protected void connect() { |
| if (connection != null) { |
| throw new TestBug("IOPipe connection is already established"); |
| } |
| |
| if (shouldStop) |
| return; |
| |
| connection = new SocketConnection(this, getName()); |
| |
| if (listening) { |
| connection.setConnectingProcess(connectingProcess); |
| if (serverSocket == null) { |
| connection.bind(port, timeout); |
| } else { |
| connection.setServerSocket(serverSocket); |
| } |
| |
| if (shouldStop) |
| return; |
| |
| // wait for connection from remote host |
| connection.accept(timeout); |
| |
| } else { |
| // attach from the debuggee's side |
| connection.continueAttach(host, port, timeout); |
| } |
| } |
| |
| /** |
| * Set ping timeout in milliseconds (0 means don't use ping at all). |
| */ |
| public void setPingTimeout(long timeout) { |
| if (connection == null) { |
| throw new TestBug("Attempt to set ping timeout for not established connection"); |
| } |
| connection.setPingTimeout(timeout); |
| } |
| |
| /** |
| * Returns value of current ping timeout in milliseconds (0 means ping is not used). |
| */ |
| public long getPingTimeout() { |
| if (connection == null) { |
| throw new TestBug("Attempt to get ping timeout for not established connection"); |
| } |
| return connection.getPingTimeout(); |
| } |
| |
| /** |
| * Perform finalization of the object by invoking close(). |
| */ |
| protected void finalize() throws Throwable { |
| close(); |
| super.finalize(); |
| } |
| |
| /** |
| * Perform finalization of the object at exit by invoking finalize(). |
| */ |
| public void finalizeAtExit() throws Throwable { |
| finalize(); |
| } |
| |
| /** |
| * Field 'pipeCounter' and method 'getNextPipeNumber' are used to construct unique names for SocketIOPipes |
| */ |
| private static int pipeCounter; |
| |
| private synchronized int getNextPipeNumber() { |
| return pipeCounter++; |
| } |
| |
| /** |
| * Construct name for SocketIOPipe if it wasn't specified |
| */ |
| private String getName() { |
| if (name == null) { |
| name = "SocketIOPipe-" + getNextPipeNumber(); |
| } |
| |
| return name; |
| } |
| } |