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

com.artos.utils.TCPServer Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (C) 2018-2019 Arpit Shah and Artos Contributors
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 ******************************************************************************/
package com.artos.utils;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import com.artos.framework.listener.RealTimeLogEventListener;
import com.artos.interfaces.Connectable;
import com.artos.interfaces.ConnectableFilter;
import com.artos.interfaces.ConnectableMessageParser;

/**
 * This class listens for client connection and accepts single client connection
 * with server
 * 
 * 
 *
 */
public class TCPServer implements Connectable {
	int nPort;
	ServerSocket tcpSocket;
	Socket serverSocket;
	BufferedReader inFromClient;
	DataOutputStream outToClient;
	Queue queue = new LinkedList();
	List filterList = null;
	RealTimeLogEventListener realTimeListener = null;
	ConnectableMessageParser msgParser = null;
	Transform _transform = new Transform();

	/**
	 * Constructor
	 * 
	 * @param nPort
	 *            Port Number, or 0 to use a port number that is automatically
	 *            allocated
	 */
	public TCPServer(int nPort) {
		this.nPort = nPort;
		this.filterList = null;
	}

	/**
	 * Constructor. Every filter adds overheads in processing received messages
	 * which may have impact on performance
	 * 
	 * @param nPort
	 *            Port Number, or 0 to use a port number that is automatically
	 *            allocated
	 * @param msgParser
	 *            parser which is used to separate relevant msgs from received
	 *            TCP byte array
	 * @param filterList
	 *            list of filters
	 */
	public TCPServer(int nPort, ConnectableMessageParser msgParser, List filterList) {
		this.nPort = nPort;
		this.msgParser = msgParser;
		this.filterList = filterList;
	}

	/**
	 * Creates a server socket, bound to the specified port. The method blocks
	 * until a connection is made.
	 * 
	 * @throws IOException
	 *             if an I/O error occurs when opening the socket.
	 */
	public void connect() throws IOException {
		// set infinite timeout by default
		connect(0);
	}

	/**
	 * Creates a server socket, bound to the specified port. The method blocks
	 * until a connection is made.
	 * 
	 * Setting soTimeout to a non-zero timeout, a call to accept() for this
	 * ServerSocket will block for only this amount of time. If the timeout
	 * expires, a java.net.SocketTimeoutException is raised, though the
	 * ServerSocket is still valid.
	 * 
	 * @param soTimeout
	 *            the specified timeout in milliseconds.
	 * @throws IOException
	 *             if an I/O error occurs when opening the socket.
	 */
	public void connect(int soTimeout) throws IOException {
		System.out.println("Listening on Port : " + nPort);

		tcpSocket = new ServerSocket(nPort);
		tcpSocket.setSoTimeout(soTimeout);
		serverSocket = tcpSocket.accept();
		if (serverSocket.isConnected()) {
			System.out.println("Connected to " + serverSocket.getInetAddress().getHostAddress() + ":" + serverSocket.getLocalPort());
		}

		// Start Reading task in parallel thread
		readFromSocket();
		notifyConnected();
	}

	/**
	 * Returns the connection state of the socket. true is returned if socket is
	 * successfully connected and has not been closed
	 */
	public boolean isConnected() {
		if (serverSocket.isConnected() && serverSocket.isBound() && !serverSocket.isClosed()) {
			return true;
		}
		return false;
	}

	/**
	 * Closes this socket. Once a socket has been closed, it is not available
	 * for further networking use (i.e. can't be reconnected or rebound). A new
	 * socket needs to be created.
	 * 
	 * @throws IOException
	 *             if an I/O error occurs when closing this socket.
	 */
	public void disconnect() throws IOException {
		serverSocket.close();
		tcpSocket.close();
		notifyDisconnected();
		System.out.println("Connection Closed");
	}

	/**
	 * Returns true if receive queue is not empty
	 */
	@Override
	public boolean hasNextMsg() {
		if (queue.isEmpty()) {
			return false;
		}
		return true;
	}

	/**
	 * Polls the queue for msg, Function will block until msg is polled from the
	 * queue or timeout has occurred. null is returned if no message received
	 * within timeout period
	 * 
	 * @param timeout
	 *            msg timeout
	 * @param timeunit
	 *            timeunit
	 * @return byte[] from queue, null is returned if timeout has occurred
	 * @throws InterruptedException
	 *             if any thread has interrupted the current thread. The
	 *             interrupted status of the current thread is cleared when this
	 *             exception is thrown.
	 */
	public byte[] getNextMsg(long timeout, TimeUnit timeunit) throws InterruptedException {
		boolean isTimeout = false;
		long startTime = System.nanoTime();
		long finishTime;
		long maxAllowedTime = TimeUnit.NANOSECONDS.convert(timeout, timeunit);

		while (!isTimeout) {
			if (hasNextMsg()) {
				return queue.poll();
			}
			finishTime = System.nanoTime();
			if ((finishTime - startTime) > maxAllowedTime) {
				return null;
			}
			// Give system some time to do other things
			Thread.sleep(10);
		}
		return null;
	}

	/**
	 * Returns byte array from the queue, null is returned if queue is empty
	 */
	@Override
	public byte[] getNextMsg() {
		if (hasNextMsg()) {
			return queue.poll();
		}
		return null;
	}

	/**
	 * Send data to client in String format
	 * 
	 * @param data
	 *            data to be sent in String format
	 * @throws IOException
	 *             if an I/O error occurs.
	 */
	public void sendMsg(String data) throws IOException {
		sendMsg(data.getBytes());
	}

	/**
	 * Send byte array to client
	 * 
	 * @throws IOException
	 *             if an I/O error occurs.
	 */
	@Override
	public void sendMsg(byte[] data) throws IOException {
		outToClient = new DataOutputStream(serverSocket.getOutputStream());
		outToClient.write(data);
		notifySend(data);
	}

	/**
	 * Clean receive queue
	 */
	public void cleanQueue() {
		queue.clear();
	}

	private void readFromSocket() {
		final ExecutorService clientProcessingPool = Executors.newFixedThreadPool(10);
		final Runnable serverTask = new Runnable() {
			@Override
			public void run() {
				try {
					clientProcessingPool.submit(new ClientTask(serverSocket, queue, realTimeListener, filterList, msgParser));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};
		Thread serverThread = new Thread(serverTask, "Artos_TCPServer_Receiver_Thread");
		serverThread.start();
	}

	// =================================================================================================
	// Listener Notify
	// =================================================================================================
	private void notifySend(byte[] data) {
		if (null != realTimeListener) {
			realTimeListener.send(data);
		}
	}

	private void notifyConnected() {
		if (null != realTimeListener) {
			realTimeListener.connected();
		}
	}

	private void notifyDisconnected() {
		if (null != realTimeListener) {
			realTimeListener.disConnected();
		}
	}

	// =================================================================================================
	// Getter Setter
	// =================================================================================================

	public ServerSocket getTcpSocket() {
		return tcpSocket;
	}

	public Socket getConnector() {
		return serverSocket;
	}

	public Queue getQueue() {
		return queue;
	}

	public int getnPort() {
		return nPort;
	}

	public void setnPort(int nPort) {
		this.nPort = nPort;
	}

	public RealTimeLogEventListener getRealTimeListener() {
		return realTimeListener;
	}

	public void setRealTimeListener(RealTimeLogEventListener realTimeListener) {
		this.realTimeListener = realTimeListener;
	}

}

/**
 * Inner Class which acts as receiver thread for incoming data. All Data will be
 * added to the Queue
 * 
 * 
 *
 */
class ClientTask implements Runnable {

	private final Socket connector;
	int read = -1;
	byte[] buffer = new byte[4 * 1024]; // a read buffer of 4KiB
	byte[] readData;
	String redDataText;
	Queue queue;
	volatile RealTimeLogEventListener realTimeListener;
	Transform _transform = new Transform();
	volatile List filterList = null;
	byte[] leftOverBytes = null;
	volatile ConnectableMessageParser msgParser = null;

	ClientTask(Socket connector, Queue queue, RealTimeLogEventListener realTimeListener, List filterList,
			ConnectableMessageParser msgParser) {
		this.connector = connector;
		this.queue = queue;
		this.realTimeListener = realTimeListener;
		this.filterList = filterList;
		this.msgParser = msgParser;
	}

	@Override
	public void run() {
		try {
			while ((read = connector.getInputStream().read(buffer)) > -1) {
				readData = new byte[read];
				System.arraycopy(buffer, 0, readData, 0, read);
				if (readData.length > 0) {
					notifyReceive(readData);

					/*
					 * If user has not provided logic for msg parsing then do
					 * simple filtering
					 */
					if (null == msgParser) {
						applyFilter(readData);
					} else {
						/*
						 * If user has provided message parsing logic then
						 * assemble any left over data from previous byte[] to
						 * readData and then put it through parsing logic to
						 * separate each messages.
						 */
						if (null != leftOverBytes) {
							readData = _transform.concat(leftOverBytes, readData);
							leftOverBytes = null;
						}
						parseIncomingData(readData);
					}
				}
			}
		} catch (Exception e) {
			if (connector.isClosed() && e.getMessage().contains("Socket closed")) {
				// Do nothing because if connector was closed then this
				// exception is as expected
			} else {
				e.printStackTrace();
			}
		}
	}

	private void parseIncomingData(byte[] readData) throws Exception {
		List msgList = msgParser.parse(readData);
		if (null != msgParser.getLeftOverBytes() && msgParser.getLeftOverBytes().length != 0) {
			leftOverBytes = msgParser.getLeftOverBytes();
		}
		
		for (byte[] msg : msgList) {
			applyFilter(msg);
		}
	}

	private void applyFilter(byte[] readData) throws Exception {
		if (null != filterList && !filterList.isEmpty()) {
			for (ConnectableFilter filter : filterList) {
				if (filter.meetCriteria(readData)) {
					// Do not add to queue if filter match is found
					return;
				}
			}
			queue.add(readData);
		} else {
			queue.add(readData);
		}
	}

	private void notifyReceive(byte[] data) {
		if (null != realTimeListener) {
			realTimeListener.receive(data);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy