/* Copyright (c) 2006-2009 Jan S. Rellermeyer * Systems Group, * Department of Computer Science, ETH Zurich. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - Neither the name of ETH Zurich nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package ch.ethz.iks.r_osgi.impl; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.IOException; import java.net.BindException; import java.net.ServerSocket; import java.net.Socket; import org.osgi.service.log.LogService; import ch.ethz.iks.r_osgi.Remoting; import ch.ethz.iks.r_osgi.URI; import ch.ethz.iks.r_osgi.channels.ChannelEndpoint; import ch.ethz.iks.r_osgi.channels.NetworkChannel; import ch.ethz.iks.r_osgi.channels.NetworkChannelFactory; import ch.ethz.iks.r_osgi.messages.RemoteOSGiMessage; import ch.ethz.iks.util.SmartObjectInputStream; import ch.ethz.iks.util.SmartObjectOutputStream; /** * channel factory for (persistent) TCP transport. This is the default protocol. * * @author Jan S. Rellermeyer, ETH Zurich * @since 0.6 */ final class TCPChannelFactory implements NetworkChannelFactory { static final String PROTOCOL = "r-osgi"; //$NON-NLS-1$ Remoting remoting; private TCPAcceptorThread thread; protected int listeningPort; /** * get a new connection. * * @param endpoint * the channel endpoint. * @param endpointURI * the URI of the remote host. * @return the transport channel. * @throws IOException */ public NetworkChannel getConnection(final ChannelEndpoint endpoint, final URI endpointURI) throws IOException { return new TCPChannel(endpoint, endpointURI); } /** * Activate the factory. Is called by R-OSGi when the factory is discovered. * * @see ch.ethz.iks.r_osgi.channels.NetworkChannelFactory#activate(ch.ethz.iks.r_osgi.Remoting) */ public void activate(final Remoting r) throws IOException { remoting = r; thread = new TCPAcceptorThread(); thread.start(); } /** * Deactivate the factory. * * @see ch.ethz.iks.r_osgi.channels.NetworkChannelFactory#deactivate(ch.ethz.iks.r_osgi.Remoting) */ public void deactivate(final Remoting r) throws IOException { if(thread != null) { thread.interrupt(); } remoting = null; } /** * get the listening port. * * @see ch.ethz.iks.r_osgi.channels.NetworkChannelFactory#getListeningPort(java.lang.String) */ public int getListeningPort(final String protocol) { return listeningPort; } /** * the inner class representing a channel with TCP transport. The TCP * connection uses the TCP keepAlive option to reduce reconnection overhead. * * @author Jan S. Rellermeyer, ETH Zurich */ private static final class TCPChannel implements NetworkChannel { /** * the socket. */ Socket socket; /** * the remote endpoint address. */ private final URI remoteEndpointAddress; /** * the local endpoint address. */ private URI localEndpointAddress; /** * the input stream. */ protected ObjectInputStream input; /** * the output stream. */ protected ObjectOutputStream output; /** * the channel endpoint. */ ChannelEndpoint endpoint; /** * connected ? */ boolean connected = true; /** * create a new TCPChannel. * * @param endpoint * the channel endpoint. * @param endpointAddress * the remote peer's URI. * @throws IOException * in case of IO errors. */ TCPChannel(final ChannelEndpoint endpoint, final URI endpointAddress) throws IOException { int port = endpointAddress.getPort(); if (port == -1) { port = 9278; } this.endpoint = endpoint; remoteEndpointAddress = endpointAddress; open(new Socket(endpointAddress.getHost(), port)); new ReceiverThread().start(); } /** * create a new TCPChannel from an existing socket. * * @param socket * the socket. * @throws IOException * in case of IO errors. */ public TCPChannel(final Socket socket) throws IOException { remoteEndpointAddress = URI.create(getProtocol() + "://" //$NON-NLS-1$ + socket.getInetAddress().getHostName() + ":" //$NON-NLS-1$ + socket.getPort()); open(socket); } /** * bind the channel to a channel endpoint. * * @param e * the channel endpoint. * * @see ch.ethz.iks.r_osgi.channels.NetworkChannel#bind(ch.ethz.iks.r_osgi.channels.ChannelEndpoint) */ public void bind(final ChannelEndpoint e) { endpoint = e; new ReceiverThread().start(); } /** * open the channel. * * @param socket * the socket. * @throws IOException * if something goes wrong. */ private void open(final Socket s) throws IOException { socket = s; localEndpointAddress = URI.create(getProtocol() + "://" //$NON-NLS-1$ + socket.getLocalAddress().getHostName() + ":" //$NON-NLS-1$ + socket.getLocalPort()); try { socket.setKeepAlive(true); } catch (final Throwable t) { // for 1.2 VMs that do not support the setKeepAlive } socket.setTcpNoDelay(true); output = new SmartObjectOutputStream(new BufferedOutputStream( socket.getOutputStream())); output.flush(); input = new SmartObjectInputStream(new BufferedInputStream(socket .getInputStream())); } /** * get the String representation of the channel. * * @return the ID. * * @see java.lang.Object#toString() */ public String toString() { return "TCPChannel (" + getRemoteAddress() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } /** * close the channel. * @throws IOException */ public void close() throws IOException { socket.close(); // receiver.interrupt(); connected = false; } /** * get the protocol that is implemented by the channel. * * @return the protocol. * @see ch.ethz.iks.r_osgi.channels.NetworkChannel#getProtocol() */ public String getProtocol() { return PROTOCOL; } /** * get the remote address. * * @see ch.ethz.iks.r_osgi.channels.NetworkChannel#getRemoteAddress() */ public URI getRemoteAddress() { return remoteEndpointAddress; } /** * get the local address. * * @see ch.ethz.iks.r_osgi.channels.NetworkChannel#getLocalAddress() */ public URI getLocalAddress() { return localEndpointAddress; } /** * send a message through the channel. * * @param message * the message. * @throws IOException * in case of IO errors. */ public void sendMessage(final RemoteOSGiMessage message) throws IOException { if (RemoteOSGiServiceImpl.MSG_DEBUG) { RemoteOSGiServiceImpl.log.log(LogService.LOG_DEBUG, "{TCP Channel} sending " + message); //$NON-NLS-1$ } message.send(output); } /** * the receiver thread continuously tries to receive messages from the * other endpoint. * * @author Jan S. Rellermeyer, ETH Zurich * @since 0.6 */ class ReceiverThread extends Thread { ReceiverThread() { setName("TCPChannel:ReceiverThread:" + getRemoteAddress()); //$NON-NLS-1$ setDaemon(true); } public void run() { while (connected) { try { final RemoteOSGiMessage msg = RemoteOSGiMessage .parse(input); if (RemoteOSGiServiceImpl.MSG_DEBUG) { RemoteOSGiServiceImpl.log.log(LogService.LOG_DEBUG, "{TCP Channel} received " + msg); //$NON-NLS-1$ } endpoint.receivedMessage(msg); } catch (final IOException ioe) { connected = false; try { socket.close(); } catch (final IOException e1) { } endpoint.receivedMessage(null); return; } catch (final Throwable t) { t.printStackTrace(); } } } } } /** * TCPThread, handles incoming tcp messages. */ protected final class TCPAcceptorThread extends Thread { /** * the socket. */ private ServerSocket socket; /** * creates and starts a new TCPThread. * * @throws IOException * if the server socket cannot be opened. */ TCPAcceptorThread() throws IOException { setName("TCPChannel:TCPAcceptorThread"); //$NON-NLS-1$ setDaemon(true); int e = 0; while (true) { try { listeningPort = RemoteOSGiServiceImpl.R_OSGI_PORT + e; socket = new ServerSocket(listeningPort); if (e != 0) { System.err .println("WARNING: Port " //$NON-NLS-1$ + RemoteOSGiServiceImpl.R_OSGI_PORT + " already in use. This instance of R-OSGi is running on port " //$NON-NLS-1$ + listeningPort); } RemoteOSGiServiceImpl.R_OSGI_PORT = listeningPort; return; } catch (final BindException b) { e++; } } } /** * thread loop. * * @see java.lang.Thread#run() */ public void run() { while (!isInterrupted()) { try { // accept incoming connections and build channel endpoints // for them remoting.createEndpoint(new TCPChannel(socket.accept())); } catch (final IOException ioe) { ioe.printStackTrace(); } } } } }