Package programming5.net.sockets

Source Code of programming5.net.sockets.TCPClient$TCPReceiver

/*
* TCPClient.java
*
* Copyright 2004 Andres Quiroz Hernandez
*
* This file is part of Programming5.
* Programming5 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Programming5 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with Programming5.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package programming5.net.sockets;

import java.net.Socket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import programming5.arrays.ArrayOperations;
import programming5.collections.MultiList;
import programming5.io.Debug;
import programming5.net.MessageArrivedEvent;
import programming5.net.MessagingClient;
import programming5.net.NetworkException;
import programming5.net.Publisher;
import programming5.net.ReceiveRequest;

/**
*This class is the TCP socket implementation of a MessagingClient. Can be used to handle several TCP connections. Messages will be sent
*to all open connections if no address is specified (messages sent to a specific address will create connections that remain open until
*the object is terminated and all connections are closed), and will be received from any connection indistinguishably. If incoming
*messages need to be distinguished by origin, separate TCPClient objects should be used for each one.
*@see programming5.net.MessagingClient
*@see programming5.net.PluggableClient
*@see java.net.Socket
*@author Andres Quiroz Hernandez
*@version 6.09
*/
public class TCPClient extends Publisher<MessageArrivedEvent> implements MessagingClient {
   
    protected List<String> pendingConnections = new ArrayList<String>();
    protected Map<String, Socket> connections = new HashMap<String, Socket>();
    protected Map<String, OutputStream> outStreams = new HashMap<String, OutputStream>();
    protected Map<String, Integer> localPorts = new HashMap<String, Integer>();
    protected Map<String, TCPReceiver> receivers = new HashMap<String, TCPReceiver>();
    protected final List<ReceiveRequest> receiveRequests = new ArrayList<ReceiveRequest>();
   
    private static final int ANYPORT = -1;
    private boolean useSeparator = true;
    protected static final byte[] SEPARATOR = ":".getBytes();
   
    /**
     *Creates an empty TCP client
     */
    public TCPClient() {
       
    }
   
    /**
     *Creates a TCP client for the specified host address at the specified port. Will bind the client to any available local port.
     */
    public TCPClient(String address, int remotePort) {
        String urlString = "//" + address + ":" + Integer.toString(remotePort);
        pendingConnections.add(urlString);
        localPorts.put(urlString, ANYPORT);
    }
   
    /**
     *Creates a TCP client on the given local port for the specified host address at the specified port.
     */
    public TCPClient(String address, int remotePort, int localPort) {
        String urlString = "//" + address + ":" + Integer.toString(remotePort);
        pendingConnections.add(urlString);
        localPorts.put(urlString, localPort);
    }
   
    /**
     *Creates a TCP client from an already open socket and immediately creates and
     *starts the receiver thread.
     */
    public TCPClient(Socket socket) throws NetworkException {
        try {
            String urlString = "//" + socket.getInetAddress().toString() + ":" + Integer.toString(socket.getPort());
            connections.put(urlString, socket);
            outStreams.put(urlString, socket.getOutputStream());
            TCPReceiver receiver = new TCPReceiver(socket, urlString);
            receivers.put(urlString, receiver);
            receiver.start();
        }
        catch (IOException e) {
            throw new NetworkException("TCPClient: Could not initialize client: " + e.getMessage());
        }
    }
   
    /**
     *Creates a TCP client from an already open socket and immediately creates and starts the receiver thread
     *@param socket the client socket
     *@param compatible if true, starts the client in compatible mode, which allows it to communicate with non-programming5 clients by
     *disabling the message separator signals in the send/receive stream; otherwise, equal to using TCPClient(Socket)
     */
    public TCPClient(Socket socket, boolean compatible) throws NetworkException {
        try {
            String urlString = "//" + socket.getInetAddress().toString() + ":" + Integer.toString(socket.getPort());
            connections.put(urlString, socket);
            outStreams.put(urlString, socket.getOutputStream());
            TCPReceiver receiver;
            if (compatible) {
                useSeparator = false;
                receiver = new CompatibleTCPReceiver(socket, urlString);
            }
            else {
                receiver = new TCPReceiver(socket, urlString);
            }
            receivers.put(urlString, receiver);
            receiver.start();
        }
        catch (IOException e) {
            throw new NetworkException("TCPClient: Could not initialize client: " + e.getMessage());
        }
    }
   
    /**
     *Creates a TCPClient that can be used to communicate with all the hosts addressed by the given urls.
     */
    public TCPClient(Collection<String> hostUrls) {
        for (String url : hostUrls) {
            pendingConnections.add(url);
            localPorts.put(url, ANYPORT);
        }
    }
   
    /**
     *Creates a TCPClient that can be used to communicate with all the hosts addressed by the given urls. The clients will be
     *bound to the local ports specified with the urls.
     */
    public TCPClient(MultiList<String, Integer> socketDescriptors) {
        for (String url : socketDescriptors.first()) {
            pendingConnections.add(url);
            localPorts.put(url, socketDescriptors.getInSecond(url));
        }
    }
   
    /**
     *Implementation of the PluggableClient interface. Opens a socket to each of the host
     *addresses with which the client has been created, gets their output streams, and starts their receiver
     *threads.
     */
    @Override
    public void establishConnection() throws NetworkException {
        if (pendingConnections.size() > 0) {
            MultiList<String, String> failedConnections = new MultiList<String, String>();
            for (String host : pendingConnections) {
                int localPort = localPorts.get(host);
                try {
                    Socket socket = connections.get(host);
                    if (socket == null) {
                        URI hostUrl = new URI(host);
                        if (localPort == ANYPORT) {
                            socket = new Socket(InetAddress.getByName(hostUrl.getHost()), hostUrl.getPort());
                        }
                        else {
                            socket = new Socket(InetAddress.getByName(hostUrl.getHost()), hostUrl.getPort(), InetAddress.getLocalHost(), localPort);
                        }
                        connections.put(host, socket);
                        outStreams.put(host, socket.getOutputStream());
                        TCPReceiver receiver = new TCPReceiver(socket, host);
                        receivers.put(host, receiver);
                        receiver.start();
                    }
                }
                catch (Exception e) {
                    failedConnections.add(host, e.getMessage());
                }
            }
            pendingConnections.clear();
            if (failedConnections.size() > 0) {
                String message = "TCPClient: The following connections could not be established:\n";
                for (String connection : failedConnections.first()) {
                    message = message + connection + ": " + failedConnections.getInSecond(connection) + "\n";
                }
                throw new NetworkException(message);
            }
        }
    }
   
    /**
     *Implementation of the MessagingClient interface
     *@param bytesMessage the packet of bytes to send to the hosts for which sockets have been opened
     */
    public void send(byte[] bytesMessage) throws NetworkException {
        if (useSeparator) {
            byte[] header = ArrayOperations.join(Integer.toString(bytesMessage.length).getBytes(), SEPARATOR);
            bytesMessage = ArrayOperations.join(header, bytesMessage);
        }
        MultiList<String, String> failedSend = new MultiList<String, String>();
        for (String host : connections.keySet()) {
            try {
                OutputStream out = outStreams.get(host);
                if (out != null) {
                    synchronized (out) {
                        out.write(bytesMessage);
                        out.flush();
                    }
                }
            }
            catch (Exception e) {
                failedSend.add(host, e.getMessage());
            }
        }
        if (failedSend.size() > 0) {
            String errorMessage = "TCPClient: The following connections could not be established:\n";
            for (String send : failedSend.first()) {
                errorMessage = errorMessage + send + ": " + failedSend.getInSecond(send) + "\n";
            }
            throw new NetworkException(errorMessage);
        }
    }

    /**
     *Implementation of the MessagingClient interface
     *@param msg the message string to send to the hosts for which sockets have been opened
     */
    public void send(String msg) throws NetworkException {
        this.send(msg.getBytes());
    }
   
    /**
     *Implementation of the MessagingClient interface
     *@param bytesMessage the message to send to the given host
     *@param url the address the message will be sent to; if a socket hasn't been opened to this address, it will be created and started
     */
    public void send(byte[] bytesMessage, String url) throws NetworkException {
        if (useSeparator) {
            byte[] header = ArrayOperations.join(Integer.toString(bytesMessage.length).getBytes(), SEPARATOR);
            bytesMessage = ArrayOperations.join(header, bytesMessage);
        }
        OutputStream out = outStreams.get(url);
        try {
            if (out == null) {
                URI hostUrl = new URI(url);
                Socket socket = new Socket(InetAddress.getByName(hostUrl.getHost()), hostUrl.getPort());
                connections.put(url, socket);
                out = socket.getOutputStream();
                outStreams.put(url, out);
                TCPReceiver receiver = new TCPReceiver(socket, url);
                receivers.put(url, receiver);
                receiver.start();
            }
            synchronized (out) {
                out.write(bytesMessage);
                out.flush();
            }
        }
        catch (Exception e) {
            throw new NetworkException("TCPClient: Could not send message to " + url + ": " + e.getMessage());
        }
    }
   
    /**
     *Implementation of the MessagingClient interface
     *@param message the message string to send to the given host
     *@param url the address the message will be sent to; if a socket hasn't been opened to this address, it will be created and started
     */
    public void send(String message, String url) throws NetworkException {
        this.send(message.getBytes(), url);
    }
   
    /**
     *Implementation of the MessagingClient interface. Blocking receive until packet
     *arrives from any of the open connections.
     *@return the packet of bytes
     */
    public byte[] receiveBytes() {
        byte[] ret = null;
        ReceiveRequest myRequest = new ReceiveRequest();
        synchronized (receiveRequests) {
            receiveRequests.add(myRequest);
        }
        myRequest.awaitUninterruptibly();
        if (myRequest.isDone()) {
            ret = myRequest.getMessage();
        }
        return ret;
    }

    /**
     *Implementation of the MessagingClient interface. Blocking receive until message
     *arrives from any of the open connections.
     *@return the message string
     */
    public String receive() {
        return new String(this.receiveBytes());
    }
   
    /**
     *Implementation of the MessagingClient interface. Blocking receive until packet
     *arrives from any of the open connections or timeout occurs.
     *@return the packetof bytes
     */
    public byte[] receiveBytes(long timeout) throws InterruptedException {
        byte[] ret = null;
        ReceiveRequest myRequest = new ReceiveRequest();
        synchronized (receiveRequests) {
            receiveRequests.add(myRequest);
        }
        myRequest.await(timeout, TimeUnit.MILLISECONDS);
        if (myRequest.isDone()) {
            ret = myRequest.getMessage();
        }
        return ret;
    }
   
    /**
     *Implementation of the MessagingClient interface. Blocking receive until message
     *arrives from any of the open connections or timeout occurs.
     *@return the message string
     */
    public String receive(long timeout) throws InterruptedException {
        return new String(this.receiveBytes(timeout));
    }
   
    /**
     *Implementation of the PluggableClient interface. Stops the receiver threads and
     *closes open sockets.
     */
    public synchronized void endConnection() {
        for (String host : outStreams.keySet()) {
            sendTerminationMessage(host);
        }
        for (String host : receivers.keySet()) {
            receivers.get(host).end();
        }
    }
   
    /**
     *Overrides method in Publisher to include the response to the receive methods
     */
    @Override
    public void fireEvent(MessageArrivedEvent event) {
        super.fireEvent(event);
        synchronized (receiveRequests) {
            for (ReceiveRequest request : receiveRequests) {
                request.setMessage(event.getContentBytes());
            }
            receiveRequests.clear();
        }
    }
   
    private synchronized void signalConnectionError(String host) {
        Debug.println("Ending connections", "programming5.net.sockets.TCPClient");
        try {outStreams.remove(host)/*.close()*/;} catch (Exception e) {e.printStackTrace();}
        try {connections.remove(host)/*.close()*/;} catch (Exception e) {e.printStackTrace();}
        this.terminateAllSubscriptions();
    }
   
    private void sendTerminationMessage(String host) {
        byte[] bytesMessage = "EOF".getBytes();
        if (useSeparator) {
            byte[] header = ArrayOperations.join(Integer.toString(-1).getBytes(), SEPARATOR);
            bytesMessage = ArrayOperations.join(header, bytesMessage);
        }
        OutputStream out = outStreams.get(host);
        if (out != null) {
            try {
                synchronized (out) {
                    out.write(bytesMessage);
                    out.flush();
                }
            }
            catch (IOException ioe) {}
        }
    }
   
    protected class TCPReceiver extends Thread {
       
        Socket socketRef;
        InputStream in;
        boolean listening = true;
        String hostname;
       
        protected static final int BUFFER_SIZE = 65536;
       
        public TCPReceiver(Socket mySocket, String myHost) throws NetworkException {
            try {
                socketRef = mySocket;
                in = mySocket.getInputStream();
                hostname = myHost;
            }
            catch (IOException e) {
                throw new NetworkException("TCPReceiver: Constructor: " + e.getMessage());
            }
        }
       
        @Override
        public void run() {
            byte[] fromServer = new byte[BUFFER_SIZE];
            byte[] bytesMsg = null; // = new byte[0];
            int bytesRead, headerPos, msgStart, msgLength, msgEnd;
            try {
                while (listening) {
                    msgStart = 0;
                    msgLength = -1;
                    msgEnd = -1;
                    headerPos = -1;
                    bytesRead = in.read(fromServer);
                    if (bytesRead > 0) {
                        bytesMsg = ArrayOperations.prefix(fromServer, bytesRead);
                    }
                    while (bytesRead > 0) {
                        try {
                            headerPos = ArrayOperations.seqFind(TCPClient.SEPARATOR[0], bytesMsg, msgStart);
                            while (headerPos <= msgStart) {
                                bytesRead += in.read(fromServer);
                                if (bytesRead > 0) {
                                    bytesMsg = ArrayOperations.join(bytesMsg, ArrayOperations.prefix(fromServer, bytesRead));
                                }
                                headerPos = ArrayOperations.seqFind(TCPClient.SEPARATOR[0], bytesMsg, msgStart);
                            }
                            bytesRead -= (headerPos - msgStart + 1);
                            msgLength = Integer.parseInt(new String(ArrayOperations.subArray(bytesMsg, msgStart, headerPos)));
                            if (msgLength >= 0) {
                                while (msgLength > bytesRead) {
                                    int moreBytesRead = in.read(fromServer);
                                    bytesRead += moreBytesRead;
                                    if (moreBytesRead > 0) {
                                        bytesMsg = ArrayOperations.join(bytesMsg, ArrayOperations.prefix(fromServer, moreBytesRead));
                                    }
                                }
                                msgStart = headerPos + 1;
                                msgEnd = msgStart + msgLength;
                                try {
                                    fireEvent(new MessageArrivedEvent(ArrayOperations.subArray(bytesMsg, msgStart, msgEnd)));
                                    bytesRead -= msgLength;
                                    msgStart = msgEnd;
                                }
                                catch (ArrayIndexOutOfBoundsException iobe) {
                                    Debug.println("TCPReceiver: Bad message received");
                                    bytesRead = -1;
                                }
                                catch (Exception e) {
                                }
                            }
                            else {
                                listening = false;
                                bytesRead = 0;
                                Debug.println("Termination message received at " + hostname, "programming5.net.sockets.TCPClient");
                                signalConnectionError(hostname);
                            }
                        }
                        catch (OutOfMemoryError oome) {
                            Runtime.getRuntime().gc();
                            Debug.println("Out of memory after reading:");
                            Debug.println("Bytes msg length: " + bytesMsg.length);
                            Debug.println("Bytes read: " + bytesRead);
                            Debug.println("Header pos: " + headerPos);
                            Debug.println("Msg start: " + msgStart);
                            Debug.println("Msg length: " + msgLength);
                            Debug.println("Msg end: " + msgEnd);
                        }
                        catch (Exception e) {
                            Debug.println("TCPReceiver: Bad message received");
                            bytesRead = -1;
                        }
                    }
                }
                end();
            }
            catch (IOException io) {
                Debug.println("TCPReceiver: Exception while receiving: " + io.getMessage());
                signalConnectionError(hostname);
            }
        }
       
        public void end() {
            try {
                socketRef.close();
                listening = false;
            }
            catch (IOException ioe) {
                Debug.printStackTrace(ioe);
            }
        }
       
    }
   
    protected class CompatibleTCPReceiver extends TCPReceiver {
       
        public CompatibleTCPReceiver(Socket mySocket, String myHost) throws NetworkException {
            super(mySocket, myHost);
        }
       
        @Override
        public void run() {
            byte[] fromServer = new byte[BUFFER_SIZE];
            byte[] bytesMsg = null;
            int bytesRead;
            try {
                while (listening) {
                    bytesRead = in.read(fromServer);
                    if (bytesRead > 0) {
                        bytesMsg = ArrayOperations.prefix(fromServer, bytesRead);
                        fireEvent(new MessageArrivedEvent(bytesMsg));
                    }
                }
                end();
            }
            catch (IOException io) {
                Debug.println("TCPReceiver: Exception while receiving: " + io.getMessage());
                signalConnectionError(hostname);
            }
        }
    }
   
}
TOP

Related Classes of programming5.net.sockets.TCPClient$TCPReceiver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.