/******************************************************************************* * Copyright (c) 2009 MATERNA Information & Communications. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html. For further * project-related information visit http://www.ws4d.org. The most recent * version of the JMEDS framework can be obtained from * http://sourceforge.net/projects/ws4d-javame. ******************************************************************************/ package org.ws4d.java.communication.connection.udp; import java.io.IOException; import org.ws4d.java.communication.DPWSProtocolData; import org.ws4d.java.communication.ProtocolData; import org.ws4d.java.communication.connection.ip.IPAddress; import org.ws4d.java.structures.HashMap; import org.ws4d.java.util.Log; import org.ws4d.java.util.TimedEntry; import org.ws4d.java.util.WatchDog; /** * UDP client which allows the sending of UDP datagram packets. */ public class UDPClient { /** * Indicates whether this client is closed or not. */ private boolean closed = false; /** * UDP listener timeout. */ private static final int UDP_RECEIVER_TIMEOUT = 20000; /** * Local host address for the UDP datagram socket. */ private IPAddress ipAddress = null; /** * Local host port for the UDP datagram socket. */ private int port = -1; private String ifaceName; /** * The listener if necessary. Used with * {@link #send(String, int, byte[], int, UDPDatagramHandler)} method. */ private UDPListener listener = null; /** * Local timeout object. */ private ListenerTimeout timeout = null; /** * Table of UDP clients. */ private static HashMap clients = new HashMap(); /** * Returns a UDP client with address and port for incoming UDP messages. If * no client exists, a new client will be created. * <p> * An UDP datagram socket has the possibility to send and receive UDP * messages, that is why it is necessary to set the local address and port * for the client.<br /> * The given address and port will be used to create the UDP datagram socket * which will receive and send UDP datagram packets. * </p> * * @param address the local address. * @param port the port. * @param ifaceName * @return the UDP client. */ public synchronized static UDPClient get(IPAddress ipAddress, int port, String ifaceName) { if (port == 0) { return new UDPClient(ipAddress, port, ifaceName); } String key = ipAddress.getAddress() + "@" + port + "%" + ifaceName; UDPClient udpc = (UDPClient) clients.get(key); if (udpc != null) return udpc; udpc = new UDPClient(ipAddress, port, ifaceName); clients.put(key, udpc); return udpc; } private UDPClient(IPAddress ipAddress, int port, String ifaceName) { this.ipAddress = ipAddress; this.port = port; this.ifaceName = ifaceName; } /** * Creates a UDP datagram socket and uses this socket to send the given data * as UDP datagram packet. * <p> * The UDP datagram socket is closed immediately after sending the UDP * datagram packet. * </p> * * @param dstAddress destination address of the UDP datagram packet. * @param dstPort destination port of the UDP datagram packet. * @param data the byte array which contains the data. * @param len the length of bytes inside the byte array which should be * sent. * @throws IOException */ public synchronized void send(IPAddress dstAddress, int dstPort, byte[] data, int len) throws IOException { if (closed) return; DatagramSocket socket = DatagramSocketFactory.getInstance().createDatagramSocket(ipAddress, port, ifaceName); send(socket, dstAddress, dstPort, data, len); socket.close(); } /** * Creates a UDP datagram socket and uses this socket to send the given data * as UDP datagram packet. * <p> * A listener is started for the created UDP datagram socket. This listener * will exist for this given time and handle incoming UDP messages for the * created UDP datagram socket. * </p> * <p> * An incoming UDP message will be forwarded to the given * {@link UDPDatagramHandler} which can handle the UDP datagram packet. * </p> * <p> * The listener should be closed with the {@link #close()} method. * </p> * * @param dstAddress destination address of the UDP datagram packet. * @param dstPort destination port of the UDP datagram packet. * @param data the byte array which contains the data. * @param len the length of bytes inside the byte array which should be * sent. * @param handler this handler will handle the incoming UDP datagram * packets. * @throws IOException */ public synchronized void send(IPAddress dstAddress, int dstPort, byte[] data, int len, UDPDatagramHandler handler, ProtocolData protocolData) throws IOException { if (closed) return; // 2011-11-12 SSch changed to allow the sending with UDPClient that does // not care about incoming data if (handler == null) { send(dstAddress, dstPort, data, len); return; } if (listener == null) { listener = new UDPListener(ipAddress, port, ifaceName, handler); if (port == 0) { port = listener.getPort(); String key = ipAddress.getAddress() + "@" + port + "%" + ifaceName; synchronized (this.getClass()) { clients.put(key, this); } } timeout = new ListenerTimeout(listener); WatchDog.getInstance().register(timeout, UDP_RECEIVER_TIMEOUT); listener.start(); } DatagramSocket socket = listener.getDatagramSocket(); // TODO replace with more generic way to set actual source port if (protocolData instanceof DPWSProtocolData) { ((DPWSProtocolData) protocolData).setSourcePort(socket.getSocketPort()); } send(socket, dstAddress, dstPort, data, len); } /** * Closes an existing UDP listener. * <p> * A listener is started if the method * {@link #send(String, int, byte[], int, UDPDatagramHandler)} is used. * </p> * * @throws IOException */ public synchronized void close() throws IOException { if (closed) return; if (listener != null) { WatchDog.getInstance().unregister(timeout); listener.stop(); listener = null; } closed = true; clients.remove(this); } /** * Returns <code>true</code> if the client is closed and cannot be used for * a request, or <code>false</code> if the client can still be used. * * @return <code>true</code> if the client is closed and cannot be used for * a request, or <code>false</code> if the client can still be used. */ public synchronized boolean isClosed() { return closed; } /** * @return the ifaceName */ public String getIfaceName() { return ifaceName; } public IPAddress getIPAddress() { return ipAddress; } public int getPort() { return port; } private void send(DatagramSocket socket, IPAddress dstAddress, int dstPort, byte[] data, int len) throws IOException { UDPServer.send(socket, dstAddress, dstPort, data, len); } /** * This timeout object is used for registration with the {@link WatchDog}. * This allows to shutdown the {@link UDPListener} after a given time. */ private class ListenerTimeout extends TimedEntry { private UDPListener listener = null; ListenerTimeout(UDPListener listener) { this.listener = listener; } protected void timedOut() { synchronized (UDPClient.this) { if (listener != null) { try { listener.stop(); } catch (IOException e) { Log.warn("Could not stop UDP listener from UDP client."); } } closed = true; } } } }