blob: b89d82b022c8a9677a88a20117c48ca55a3516c8 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2021 the Eclipse BaSyx Authors
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
******************************************************************************/
package org.eclipse.basyx.components.netcomm;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
/**
* Implements an NIO TCP server
*
* This server supports both blocking and non-blocking operation. It manages a single communication stream to a client.
* Communication messages consist of a 32 Bit value that describes message length (bytes), followed by message length bytes
* with payload.
*
* @author kuhn
*
*/
public class TCPServer extends TCPCommunicator implements Runnable {
/**
* Port number
*/
protected int port = -1;
/**
* Implements non-blocking semantics
*/
protected boolean isBlocking = true;
/**
* Server socket
*/
protected ServerSocketChannel serverSocket = null;
/**
* Create a connection server on given server port
*/
public TCPServer(int portNo) {
// Store port number
port = portNo;
// Catch communication errors
try {
// Resolve address of this host
InetAddress hostIPAddress = InetAddress.getByName("localhost");
// Server socket channel
serverSocket = ServerSocketChannel.open();
serverSocket.configureBlocking(true);
serverSocket.socket().bind(new InetSocketAddress(hostIPAddress, port));
} catch (IOException e) {
// Output exception
e.printStackTrace();
}
}
/**
* Make socket non blocking
*/
public void makeNonBlocking() {
// Set my own blocking flag
isBlocking = false;
// Change server socket
try {serverSocket.configureBlocking(false);} catch (IOException e) {e.printStackTrace();}
}
/**
* Add server socket (the one that receives 'accept' notifications) to given selector
*
* @return The selector, same as input parameter
*/
public Selector addServerSocket(Selector selector) {
// Register 'accept' event
try {serverSocket.register(selector, SelectionKey.OP_ACCEPT);} catch (IOException e) {e.printStackTrace();}
// Return selector
return selector;
}
/**
* Accept an incoming connection
*/
public void acceptIncomingConnection() throws IOException {
// Only accept connection if no connection is waiting
if (!((communicationToClient == null) || (!communicationToClient.isConnected()))) return;
// Try to accept connection. Return communication socket if successful
communicationToClient = serverSocket.accept();
// Make communication nonblocking if blocking flag is not set
if (!isBlocking) communicationToClient.configureBlocking(false);
}
/**
* Close server communication
*/
public void closeServer() {
try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}
}
/**
* Server main loop (for non-blocking communication)
*/
@Override
public void run() {
// Check if we are still using blocking communication
if (!isBlocking) throw new RuntimeException("TCP Server communication thread may only be used on blocking sockets.");
// Wait for a connection
while (true) {
// Exception handling
try {
// Wait for new connection if necessary
while (((communicationToClient == null) || (!communicationToClient.isConnected()))) acceptIncomingConnection();
// Stop if server channel is closed
if (!serverSocket.isOpen()) break;
// Read data
byte[] message = readMessage();
// Notify listeners
if (message != null) notifyListeners(message);
} catch (ClosedChannelException e) {
// Server socket has been closed
return;
} catch (IOException e) {
// Print exception, continue
e.printStackTrace();
}
}
}
}