All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cosylab.epics.caj.CARepeater Maven / Gradle / Ivy

Go to download

JCA is an EPICS Channel Access library for Java. For more information concerning EPICS or Channel Access please refer to the <a href="http://www.aps.anl.gov/epics">EPICS Web pages</a> or read the <a href="http://www.aps.anl.gov/epics/base/R3-14/8-docs/CAref.html">Channel Access manual (3.14)</a>. <p>This module also includes CAJ, A 100% pure Java implementation of the EPICS Channel Access library.</p>

There is a newer version: 2.4.10
Show newest version
/*
 * Copyright (c) 2004 by Cosylab
 *
 * The full license specifying the redistribution, modification, usage and other
 * rights and obligations is included with the distribution of this project in
 * the file "LICENSE-CAJ". If the license is not included visit Cosylab web site,
 * .
 *
 * THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, NOT EVEN THE
 * IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, ASSUMES
 * _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE RESULTING FROM THE USE, MODIFICATION,
 * OR REDISTRIBUTION OF THIS SOFTWARE.
 */

package com.cosylab.epics.caj;

import java.io.File;
import java.net.BindException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.cosylab.epics.caj.impl.CAConstants;
import com.cosylab.epics.caj.util.InetAddressUtil;
import com.cosylab.epics.caj.util.logging.ConsoleLogHandler;

/**
 * CA repeater. 
 * @author Matej Sekoranja
 * @version $id$
 */
public class CARepeater implements Runnable
{
	
	// Get Logger
	private static final Logger logger2 = Logger.getLogger(CARepeater.class.getName());
	
	static
	{
		// force only IPv4 sockets, since EPICS does not work right with IPv6 sockets
		// see http://java.sun.com/j2se/1.5.0/docs/guide/net/properties.html
		System.setProperty("java.net.preferIPv4Stack", "true");
	}
	
    /**
     * System JVM property key to force native repeater. 
     */
    public static final String CA_FORCE_NATIVE_REPEATER = "CA_FORCE_NATIVE_REPEATER";

    /**
     * System JVM property key to disable CA repeater. 
     */
    public static final String CA_DISABLE_REPEATER = "CA_DISABLE_REPEATER";

    /**
	 * CA repeater client.
	 */
	class Client {
		
		/**
		 * Client address.
		 */
		private InetSocketAddress clientAddress;
		
		/**
		 * Client address.
		 */
		private DatagramSocket clientSocket = null;

		/**
		 * Constructor.
		 * @param clientAddress
		 */
		public Client(InetSocketAddress clientAddress)
		{
			this.clientAddress = clientAddress;
		}
		
		/**
		 * Connect.
		 * @return success flag.
		 */
		public boolean connect()
		{
			try {
				clientSocket = createDatagramSocket();
				clientSocket.connect(clientAddress);
			}
			catch (Throwable th) {
				// failed to connect
				logger.log(Level.FINEST, "Failed to connect to: " + clientAddress, th);
				return false;
			}
			return true;
		}
		
		/**
		 * Destroy client (close socket).
		 */
		public void destroy()
		{
			if (clientSocket != null)
				clientSocket.close();
		}
		
		/**
		 * Client address.
		 * @return client address.
		 */
		public InetSocketAddress getClientAddress() {
			return clientAddress;
		}
		
		/**
		 * Verify the state of the socket.
		 * @return verification success.
		 */
		public boolean verify()
		{
			try
			{
				// this should fail, if client is listening
				DatagramSocket socket = createDatagramSocket(clientAddress.getPort(), false);
				socket.close();
				logger.log(Level.FINEST, "Dead client detected: " + clientAddress);
				return false;
			} catch (Throwable th) {
				// this is OK
				return true;
			}
		}
		
		/**
		 * Send packet.
		 * @param packet
		 * @return success status.
		 */
		public boolean send(DatagramPacket packet)
		{
			packet.setSocketAddress(clientAddress);
			try {
				logger.log(Level.FINEST, "Sending packet to: " + clientAddress);
				clientSocket.send(packet);
			} catch (Throwable th) {
				// failed to send
				logger.log(Level.FINEST, "Failed to send packet to: " + clientAddress, th);
				return false;
			}
			return true;
		}
		
		/**
		 * Send repeater confirm message.
		 * @return confirmation send success status.
		 */
		public boolean sendConfirm()
		{
			// build REPEATER_CONFIRM message
			byte[] message = new byte[CAConstants.CA_MESSAGE_HEADER_SIZE];
			ByteBuffer buffer = ByteBuffer.wrap(message);
			buffer.putShort(COMMAND_OFFSET, REPEATER_CONFIRM);
			buffer.putInt(AVAILABLE_OFFSET, InetAddressUtil.ipv4AddressToInt(clientAddress.getAddress()));
			
			// send
			DatagramPacket packet = new DatagramPacket(message, message.length);
			return send(packet);
		}
	}

	// message field codes
	private static final int COMMAND_OFFSET = 0;
    private static final int AVAILABLE_OFFSET = 12;

    // message command codes
    private static final short CA_PROTO_VERSION = 0;
    private static final short REPEATER_REGISTER = 24;
    private static final short REPEATER_CONFIRM = 17;
    private static final short CA_PROTO_RSRV_IS_UP = 13;

    /**
	 * Context logger.
	 */
	protected Logger logger = Logger.global;

    /**
	 * Repeater port.
	 */
	protected int repeaterPort = CAConstants.CA_REPEATER_PORT;
		
	/**
	 * Local unbounded DatagramSocket.
	 */
	protected DatagramSocket localDatagramSocket = null;

	/**
	 * List of registered clients.
	 */
	protected List clients = new ArrayList();

	/**
	 * Constructor.
	 */
	public CARepeater()
	{
		// read configuration, repeater port
		String port = System.getProperty("EPICS_CA_REPEATER_PORT");
		if (port != null)
		{
			try {
				repeaterPort = Integer.parseInt(port);
			} catch (NumberFormatException nfe) {
				logger.log(Level.FINE, "Failed to parse repeater port '" + port + "'.", nfe);
			}
		}
		
		initialize();
	}
	
	/**
	 * Constructor.
	 * @param repeaterPort	repeater port.
	 */
	public CARepeater(int repeaterPort)
	{
		this.repeaterPort = repeaterPort;
		initialize();
	}

	/**
	 * Initialize CA repeater.
	 */
	protected void initialize()
	{
		initializeLogger();
	}

	/**
	 * Initialize context logger.
	 */
	protected void initializeLogger()
	{
		String loggerName = this.getClass().getName();
		logger = Logger.getLogger(loggerName);
		
		if (System.getProperties().containsKey(CAJConstants.CAJ_DEBUG))
		{
			logger.setLevel(Level.FINE);
			logger.addHandler(new ConsoleLogHandler());
		}
	}
	
	/**
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		process();
	}

	protected void registerNewClient(InetSocketAddress clientAddress)
	{
		logger.fine("Registering client: " + clientAddress);

		final int INADDR_LOOPBACK = 0x7F000001;
		if (InetAddressUtil.ipv4AddressToInt(clientAddress.getAddress()) != INADDR_LOOPBACK)
		{
			// create local datagram socket
			if (localDatagramSocket == null)
			{
				try {
					localDatagramSocket = createDatagramSocket();
				} catch (Throwable th) {
					logger.log(Level.FINEST, "Failed to create local test datagram socket.", th);
				}
			}

			// try to bind it to a unbounded clientAddress, if it is local it will succeed
			if (localDatagramSocket != null)
			{
				final int PORT_ANY = 0;
				
				try
				{
					// try...
					localDatagramSocket.bind(new InetSocketAddress(clientAddress.getAddress(), PORT_ANY));
					
					// close on success
					// multiple bounds not allowed by Java, so we will force recreate
					localDatagramSocket.close();
					localDatagramSocket = null;

				} catch (Throwable th) {
					// failed to connect, reject remote client
					return;
				}

			}
			else
			{
				// failed to do the test, reject remote (assumed) client
				return;
			}
		}

		Client client = null;
		// check if already registered
		synchronized (clients)
		{
			// do not waste resources, if nobody to send
			if (clients.size() != 0)
			{
				Iterator iter = clients.iterator();
				while (iter.hasNext())
				{
					Client c = (Client)iter.next();
					if (c.getClientAddress().getPort() == clientAddress.getPort())
					{
						client = c;
						break;
					}
				}
			}
		}

		boolean newClient = false;
		
		// create new, if necessary
		if (client == null)
		{
			client = new Client(clientAddress);
			if (!client.connect())
			{
				client.destroy();
				return;
			}
			
			// add
			synchronized (clients)
			{
				clients.add(client);
			}
			
			newClient = true;
		}
		
		// send repeater confirm
		if (!client.sendConfirm())
		{
			// add
			synchronized (clients)
			{
				clients.remove(client);
			}
			client.destroy();
		}
		
		logger.fine("Client registered: " + clientAddress);
		
		// send noop message to all other clients, not to accumulate clients
		// when there are no beacons
		byte[] message = new byte[CAConstants.CA_MESSAGE_HEADER_SIZE];
		ByteBuffer buffer = ByteBuffer.wrap(message);
		buffer.putShort(COMMAND_OFFSET, CA_PROTO_VERSION);
		fanOut(clientAddress, buffer);
		
		// verify all clients
		if (newClient)
			verifyClients();

	}
	
	protected void fanOut(InetSocketAddress fromAddress, ByteBuffer buffer)
	{
		synchronized (clients)
		{
			// do not waste resources, if nobody to send
			if (clients.size() == 0)
				return;
			
			// create packet to send, send address still needs to be set
			DatagramPacket packetToSend = 
				new DatagramPacket(buffer.array(), buffer.position(), buffer.limit());
			
			Iterator iter = clients.iterator();
			while (iter.hasNext())
			{
				Client client = (Client)iter.next();
				
				// don't reflect back to sender
				if (client.getClientAddress().equals(fromAddress))
					continue;

				// send, send, send...
				if (!client.send(packetToSend))
				{
					// check if socket is valid
					if (!client.verify())
					{
						// destory and remove
						client.destroy();
						iter.remove();
					}
				}
				
			}
		}
	}

	/**
	 * Verify all the clients.
	 */
	protected void verifyClients()
	{
		synchronized (clients)
		{
			// do not waste resources, if nobody to send
			if (clients.size() == 0)
				return;
			
			Iterator iter = clients.iterator();
			while (iter.hasNext())
			{
				Client client = (Client)iter.next();
				
				// check if socket is valid
				if (!client.verify())
				{
					// destory and remove
					client.destroy();
					iter.remove();
				}
			}
		}
	}

	/**
	 * Process UDP requests. 
	 */
	protected void process() 
	{
		DatagramSocket socket = null;
		try
		{
			logger.fine("Initializing CA repeater.");

		    // Create a buffer to read datagrams into. If a packet is 
			// larger than this buffer, the excess will simply be discarded.
		    byte[] buffer = new byte[CAConstants.MAX_UDP_RECV];
		    ByteBuffer data = ByteBuffer.wrap(buffer);

		    // create a packet to receive data into the buffer
		    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
		
		    // create and bind datagram socket
			try
			{
				socket = createDatagramSocket(repeaterPort, true);
			} catch (BindException be) {
				// notrify and finish
				logger.log(Level.FINE, "Failed to bind.", be);
				return;
			}
			logger.fine("Binded to UDP socket: " + socket.getLocalSocketAddress());
			
			logger.fine("CA repeater attached and initialized.");
			
			while (true)
			{
				// wait to receive a datagram
			    data.clear();
			    socket.receive(packet);

			    InetSocketAddress receivedFrom = (InetSocketAddress)packet.getSocketAddress();
			    	
			    int bytesReceived = packet.getLength();
			    data.limit(bytesReceived);
			    if (bytesReceived >= CAConstants.CA_MESSAGE_HEADER_SIZE)
			    {
			    	short command = data.getShort(COMMAND_OFFSET);
			    	// register request message
			    	if (command == REPEATER_REGISTER)
			    	{
			    		registerNewClient(receivedFrom);
			    		
			    		// skip this header, process rest if any left
			    		data.position(CAConstants.CA_MESSAGE_HEADER_SIZE);
			    		if (!data.hasRemaining())
			    			continue;
			    	}
			    	// beacon
			    	else if (command == CA_PROTO_RSRV_IS_UP)
			    	{
			    		// set address, if missing
				    	short address = data.getShort(AVAILABLE_OFFSET); 
				    	if (address == 0)
			    		{
			    			data.putInt(AVAILABLE_OFFSET, 
			    						InetAddressUtil.ipv4AddressToInt(packet.getAddress()));
			    		}
			    	}
			    }
			    // empty message request registers too
			    else if (bytesReceived == 0)
			    {
		    		registerNewClient(receivedFrom);
		    		continue;
			    }
			    
			    // fan out packet
			    fanOut(receivedFrom, data);
			}
			
		}
		catch (Throwable th)
		{
			logger.log(Level.SEVERE, "Unexpected exception caught.", th);
		}
		finally
		{
			if (socket != null)
				socket.close();
		}
	}
	
	/**
	 * Constructs an unbound datagram socket.
	 * @return default unbound datagram socket.
	 * @throws SocketException - if the socket could not be opened, or the socket could not bind to the specified local port.
	 */
	protected static DatagramSocket createDatagramSocket()
		throws SocketException 
	{
		return new DatagramSocket(null);
	}

	/**
	 * Constructs a atagram socket bound to the wildcard address on defined port.
	 * @param port InetSocketAddress
	 * @param reuseAddress Enable/disable the SO_REUSEADDR socket option
	 * @throws SocketException - if the socket could not be opened, or the socket could not bind to the specified local port.
	 * @return default bounded datagram socket.
	 */
	protected static DatagramSocket createDatagramSocket(int port, boolean reuseAddress)
		throws SocketException
	{
		DatagramSocket socket = new DatagramSocket(null);
		
		socket.bind(new InetSocketAddress(port));
		socket.setReuseAddress(reuseAddress);
		
		return socket;
	}
	
	/**
	 * Check if repeater is running.
	 * @param repeaterPort repeater port.
	 * @return true if repeater is already running, false otherwise
	 */
	protected static boolean isRepeaterRunning(int repeaterPort)
	{
	    // test if repeater is already running, by binding to its port
		try
		{
			DatagramSocket socket = createDatagramSocket(repeaterPort, true);
			socket.close();
			// bind succeeded, repeater not running
			return false;
		} catch (BindException be) {
			// bind failed, socket in use
			return true;
		} catch (SocketException se) {
			// Win7 "version" of: bind failed, socket in use
			return true;
		} catch (Throwable th) {
			// unexpected error
			logger2.log(Level.WARNING, "", th);
			return false;
		}
	}
	
	/**
	 * Start repeater as detached process.
	 * First checks if repeater is already running, if not
	 * other JVM process is run.
	 * @param repeaterPort repeater port.
	 * @throws Throwable throwable
	 */
	public static void startRepeater(final int repeaterPort) throws Throwable
	{
		// disable repeater check
	    if (System.getProperties().containsKey(CA_DISABLE_REPEATER))
	        return;

	    // force native repeater check
	    if (System.getProperties().containsKey(CA_FORCE_NATIVE_REPEATER))
	    {
	    	JNIRepeater.repeaterInit();
	        return;
	    }

	    if (repeaterPort <= 0)
			throw new IllegalArgumentException("port must be > 0");
		
		// nothing to do, if repeater is already running
		if (isRepeaterRunning(repeaterPort))
			return;

		PrivilegedAction action = new PrivilegedAction() {
			
			public Object run() {

				// java.home java.class.path
				final String[] commandLine = new String[] { 
						System.getProperty("java.home") + File.separator + "bin" + File.separator + "java",
						"-classpath",
						System.getProperty("java.class.path"), 
						CARepeater.class.getName(),
						"-p",
						String.valueOf(repeaterPort)
				};

				try {
					Runtime.getRuntime().exec(commandLine);
				} catch (Throwable th) {
					System.err.println("Failed to exec '" + commandLine[0] + "', trying to start native repeater...");
					logger2.log(Level.SEVERE, "Failed to exec '" + commandLine[0] + "', trying to start native repeater...", th);

					try {
						//  fallback: try to run native repeater
						JNIRepeater.repeaterInit();
					} catch (Throwable th2) {
						System.err.println("Failed to start native repeater.");
						logger2.log(Level.SEVERE, "Failed to start native repeater.", th);
					}
				}

				return null;
			}
		};

		Object res = AccessController.doPrivileged(action);
		if (res != null)
			throw new Exception("Unable to init CA Repeater", (Throwable) res);

	}
	
	
	/**
	 * Main entry-point.
	 * @param argv arguments.
	 */
	public static void main(String argv[])
	{
		CARepeater repeater;
		
		// check for port argument
		int port = -1;
		if (argv.length >= 2 &&
			(argv[0].equals("-p") || argv[0].equals("--port")))
		{
			try {
				port = Integer.parseInt(argv[1]);
			} catch (NumberFormatException nfe) {
				System.err.println("Failed to parse repeater port '" + argv[1] + "'.");
			}
		}
		
		// create repeater
		if (port > 0)
			repeater = new CARepeater(port);
		else
			repeater = new CARepeater();
		
		// run, run, run...
		repeater.run();
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy