/*******************************************************************************
* gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/
* Copyright (C) 2014 SVS
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package userGeneratedContent.testbedPlugIns.layerPlugIns.layer1network.cascade_TCP_v0_001;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import staticContent.framework.controller.SubImplementation;
import staticContent.framework.message.Request;
import staticContent.framework.routing.RoutingMode;
import staticContent.framework.userDatabase.User;
import staticContent.framework.userDatabase.UserAttachment;
import staticContent.framework.util.Util;
// MP12-13
// TODO: may block in duplex mode...
public class ClientHandler_TCP_FCFS_async_nio_new extends SubImplementation {
/* properties */
private int PORT;
private InetAddress MIX_BIND_ADDRESS;
private int BACKLOG;
private int SO_TIMEOUT;
private int MAX_REQUEST_LENGTH;
private int QUEUE_BLOCK_SIZE;
private int MAX_READS_IN_A_ROW;
@Override
public void constructor() {
if (anonNode.ROUTING_MODE != RoutingMode.GLOBAL_ROUTING)
throw new RuntimeException("this is a cascade plug-in; ROUTING_MODE is not set to GLOBAL_ROUTING -> will exit now");
this.MIX_BIND_ADDRESS = settings.getPropertyAsInetAddress("GLOBAL_MIX_BIND_ADDRESS");
this.PORT = settings.getPropertyAsInt("GLOBAL_MIX_BIND_PORT");
this.BACKLOG = settings.getPropertyAsInt("BACKLOG");
this.SO_TIMEOUT = settings.getPropertyAsInt("SO_TIMEOUT");
this.MAX_REQUEST_LENGTH = settings.getPropertyAsInt("MAX_REQUEST_LENGTH");
this.QUEUE_BLOCK_SIZE = settings.getPropertyAsInt("GLOBAL_QUEUE_BLOCK_SIZE");
this.MAX_READS_IN_A_ROW = settings.getPropertyAsInt("MAX_READS_IN_A_ROW");
}
@Override
public void initialize() {
}
@Override
public void begin() {
new NIOLoop().start();
}
/**
* Main loop which selects the nio events.
*
* @author Christopher Bartz
*/
private class NIOLoop extends Thread {
Selector selector;
ServerSocketChannel serverSocketChannel;
List<Request> requestList;
NIOLoop() {
this.setName("NIOLoop");
this.requestList = new ArrayList<Request>(QUEUE_BLOCK_SIZE);
}
@Override
public void run() {
try {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
InetSocketAddress endpoint = new InetSocketAddress(MIX_BIND_ADDRESS, PORT);
serverSocket.bind(endpoint, BACKLOG);
serverSocket.setSoTimeout(SO_TIMEOUT);
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Could not open ServerSocket");
}
while (true) {
try {
selector.select();
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey sk = selectedKeys.next();
selectedKeys.remove();
int readyOps = sk.readyOps();
if ((readyOps & SelectionKey.OP_ACCEPT) != 0) {
handleAcceptRequest();
}
if ((readyOps & SelectionKey.OP_READ) != 0) {
handleReadRequest(sk);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Handles an accept request and generates a
* {@link staticContent.framework.userDatabase.User} object and adds it to the
* {@link framework.core.userDatabase.UserDataBase}.
*/
private void handleAcceptRequest() {
try {
SocketChannel sc = serverSocketChannel.accept();
sc.configureBlocking(false);
User user = userDatabase.generateUser();
userDatabase.addUser(user);
sc.register(selector, SelectionKey.OP_READ,
new UserChannelData(user, this, sc));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Tries to read requests from the channel associated with the provided
* {@link java.nio.channels.SelectionKey}.
*
* @param sk
* @throws IOException
*/
private void handleReadRequest(SelectionKey sk) throws IOException {
UserChannelData userData = (UserChannelData) sk.attachment();
Request request;
try {
/*
* store up to MAX_READS_IN_A_ROW messages in a list of size
* QUEUE_BLOCK_SIZE
*/
for (int i = 1; i <= MAX_READS_IN_A_ROW; i++) {
request = tryToReadRequest(userData);
if (request != null) {
requestList.add(request);
if ((i % QUEUE_BLOCK_SIZE) == 0) {
anonNode.putInRequestInputQueue(requestList
.toArray(new Request[0]));
requestList.clear();
}
} else {
break;
}
}
if (requestList.size() > 0) {
anonNode.putInRequestInputQueue(requestList
.toArray(new Request[0]));
requestList.clear();
}
} catch (UnsupportedOperationException e) {
e.printStackTrace();
requestList = new ArrayList<Request>(QUEUE_BLOCK_SIZE);
}
}
/**
* Reads a {@link staticContent.framework.message.Request} from a
* {@link staticContent.framework.userDatabase.User}.
*
* @param userData
* the data of the user
* @return a Request or null if a Request could not be read.
* @throws IOException
*/
private Request tryToReadRequest(UserChannelData userData)
throws IOException {
SocketChannel sc = userData.socketChannel;
if (userData.receivedData == null)
userData.receivedData = ByteBuffer.allocate(4);
if (!userData.requestLengthHeaderRead) {
if (sc.read(userData.receivedData) == -1) {
throw new IOException("warning: Connection lost to user " + userData.getOwner());
}
if (userData.receivedData.hasRemaining()) {
/* then we did not read the full header */
return null;
} else {
userData.requestLengthHeaderRead = true;
userData.receivedData.flip();
byte[] lengthHeader = new byte[4];
userData.receivedData.get(lengthHeader);
int msgLength = Util.byteArrayToInt(lengthHeader);
if (msgLength > MAX_REQUEST_LENGTH)
throw new IOException("warning: user " + userData.getOwner() + " sent a too large message");
userData.receivedData = ByteBuffer.allocate(msgLength);
}
}
if (sc.read(userData.receivedData) == -1) {
throw new IOException("warning: Connection lost to user "
+ userData.getOwner());
}
if (userData.receivedData.hasRemaining()) {
/* then we did not read the full message */
return null;
} else {
byte[] msg = new byte[userData.receivedData.capacity()];
userData.receivedData.flip();
userData.receivedData.get(msg);
userData.receivedData = null;
userData.requestLengthHeaderRead = false;
return Request.getInstanceRequest(msg, userData.getOwner());
}
}
}
/**
* Stores the channel of a user and incomplete data (= a message has not
* been received completely).
*
* @author Christopher Bartz
*
*/
private class UserChannelData extends UserAttachment {
ByteBuffer receivedData;
SocketChannel socketChannel;
boolean requestLengthHeaderRead = false;
public UserChannelData(User owner, Object callingInstance,
SocketChannel socketChannel) {
super(owner, callingInstance);
this.socketChannel = socketChannel;
}
}
}