/* * 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 IOPipe 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 IOPipe 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 IOPipe 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 line to this * IOPipe 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 IOPipe channel, * or return null 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 IOPipe connection. */ public void close() { shouldStop = true; if (connection != null) { connection.close(); } } /** * Establish IOPipe 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; } }