/******************************************************************************* * 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.Iterator; import java.util.LinkedList; import java.util.Vector; import java.util.concurrent.LinkedBlockingQueue; import staticContent.framework.controller.SubImplementation; import staticContent.framework.message.MixMessage; import staticContent.framework.message.Reply; import staticContent.framework.message.Request; import staticContent.framework.userDatabase.DatabaseEventListener; import staticContent.framework.userDatabase.User; import staticContent.framework.userDatabase.UserAttachment; import staticContent.framework.util.Util; public class ClientHandler_TCP_FCFS_async_nio2 extends SubImplementation implements DatabaseEventListener { //TODO: add timeout for inactive users private int port; private InetAddress bindAddress; private int backlog; private int soTimeout; private int maxConnections; private Integer numberOfActiveConnections = 0; private Selector selector = null; private ServerSocketChannel serverSocketChannel; //private int queueBlockSize; private NIOLoop nioLoop; private RequestSender requestSender; private ReplyFetcher replyFetcher; private ReplySender replySender; private LinkedBlockingQueue<SelectionKey> dataAvailableToReadEvents; private LinkedBlockingQueue<SelectionKey> dataAvailableToWriteEvents; private LinkedList<UserChannelData> usersWithRepliesReady; private Object sync = new Object(); private int maxRequestLength; private int maxMessages; private volatile int messageCounterReplies = 0; private volatile boolean replyFetcherSleeping = false; @Override public void constructor() { this.bindAddress = settings.getPropertyAsInetAddress("GLOBAL_MIX_BIND_ADDRESS"); this.port = settings.getPropertyAsInt("GLOBAL_MIX_BIND_PORT"); this.backlog = settings.getPropertyAsInt("BACKLOG"); this.soTimeout = settings.getPropertyAsInt("SO_TIMEOUT"); this.maxConnections = settings.getPropertyAsInt("MAX_CONNECTIONS"); this.maxRequestLength = settings.getPropertyAsInt("MAX_REQUEST_LENGTH"); this.maxMessages = settings.getPropertyAsInt("MAX_MESSAGES_IN_IOH"); //this.queueBlockSize = settings.getPropertyAsInt("QUEUE_BLOCK_SIZE"); this.userDatabase.registerEventListener(this); this.dataAvailableToReadEvents = new LinkedBlockingQueue<SelectionKey>(); this.dataAvailableToWriteEvents = new LinkedBlockingQueue<SelectionKey>(); this.usersWithRepliesReady = new LinkedList<UserChannelData>(); this.nioLoop = new NIOLoop(); this.requestSender = new RequestSender(); if (anonNode.IS_DUPLEX) { this.replyFetcher = new ReplyFetcher(); this.replySender = new ReplySender(); } } @Override public void initialize() { // TODO Auto-generated method stub } @Override public void begin() { this.nioLoop.start(); this.requestSender.start(); if (anonNode.IS_DUPLEX) { this.replyFetcher.start(); this.replySender.start(); } } private class NIOLoop extends Thread { @Override public void run() { openServerSocket(); SelectionKey key = null; while (true) { // handle read, write and accept events /*System.out.println("NIOLoop-Status: "); System.out.println(" usersWithRepliesReady.size(): " +usersWithRepliesReady.size()); System.out.println(" dataAvailableToReadEvents.size(): " +dataAvailableToReadEvents.size()); System.out.println(" dataAvailableToWriteEvents.size(): " +dataAvailableToWriteEvents.size()); System.out.println(" numberOfActiveConnections: " +numberOfActiveConnections); System.out.println(" messageCounterReplies: " +messageCounterReplies); */ try { // notify selector about replies ready to be sent (to clients) synchronized (sync) { Iterator<UserChannelData> users = usersWithRepliesReady.iterator(); while (users.hasNext()) { UserChannelData userData = users.next(); if (!userData.valid) continue; SelectionKey selectionkey = userData.socketChannel.keyFor(selector); selectionkey.interestOps(SelectionKey.OP_WRITE); } // delete old writeRequests if (usersWithRepliesReady.size() > 0) { usersWithRepliesReady.clear(); } } // wait for event(s) selector.select(); // handle events Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator(); while (selectedKeys.hasNext()) { key = selectedKeys.next(); selectedKeys.remove(); if (!key.isValid()) continue; if (key.isAcceptable()) { handleAcceptRequest(); } else if (key.isReadable()) { dataAvailableToReadEvents.put(key); } else if (key.isWritable()) { dataAvailableToWriteEvents.put(key); } } } catch (InterruptedException e) { e.printStackTrace(); continue; } catch (IOException e) { e.printStackTrace(); if (key != null && key.attachment() instanceof UserChannelData) { removeUser((UserChannelData)key.attachment()); key.cancel(); } if (replyFetcher != null) replyFetcher.wakeup(); continue; } } } } // end NIOLoop private class ReplyFetcher extends Thread { @Override public void run() { while (true) { // wait for data from recoding scheme, store it in UserChannelData, add reference on UserChannelData in usersWithRepliesReady and wake up selector (which will send the data later) Reply[] reply = anonNode.getFromReplyOutputQueue(); // blocks until a reply is available for (int i=0; i<reply.length; i++) { // block if message limit reached synchronized (sync) { while (messageCounterReplies >= maxMessages) { replyFetcherSleeping = true; try { sync.wait(); } catch (InterruptedException e) { e.printStackTrace(); continue; } replyFetcherSleeping = false; } UserChannelData userData = reply[i].getOwner().getAttachment(getThis(), UserChannelData.class); if (!userData.valid) continue; messageCounterReplies++; userData.dataToSend.add(ByteBuffer.wrap(Util.concatArrays(Util.intToByteArray(reply[i].getByteMessage().length), reply[i].getByteMessage()))); usersWithRepliesReady.add(userData); } // wake up selector so it can send the data selector.wakeup(); } } } public void wakeup() { synchronized (sync) { if (replyFetcherSleeping) sync.notifyAll(); } } } // end ReplyFetcher private class ReplySender extends Thread { @Override public void run() { SelectionKey key = null; while (true) { try { // wait for data from useres key = dataAvailableToWriteEvents.take(); // might block synchronized (sync) { UserChannelData userdata = (UserChannelData)key.attachment(); if (!userdata.valid) continue; int repliesWritten = tryToSendReplies(userdata); messageCounterReplies -= repliesWritten; if (repliesWritten > 0) replyFetcher.wakeup(); if (userdata.dataToSend.size() == 0) key.interestOps(SelectionKey.OP_READ); } } catch (InterruptedException e) { e.printStackTrace(); continue; } catch (IOException e) { e.printStackTrace(); if (key != null && key.attachment() instanceof UserChannelData) { removeUser((UserChannelData)key.attachment()); key.cancel(); } replyFetcher.wakeup(); continue; } } } } private class RequestSender extends Thread { @Override public void run() { SelectionKey key = null; while (true) { try { // wait for data from useres key = dataAvailableToReadEvents.take(); // might block // convert received data to Request-objects (if enough data available) Vector<Request> requests; UserChannelData userdata = (UserChannelData)key.attachment(); if (!userdata.valid) continue; requests = tryToReadRequests(userdata); // make data available to recoding scheme via inputOutputHandlerInternal anonNode.putInRequestInputQueue(requests.toArray(new Request[0])); // might block } catch (InterruptedException e) { e.printStackTrace(); continue; } catch (IOException e) { e.printStackTrace(); if (key != null && key.attachment() instanceof UserChannelData) { removeUser((UserChannelData)key.attachment()); key.cancel(); } if (replyFetcher != null) replyFetcher.wakeup(); continue; } } } } // end RequestSender private class UserChannelData extends UserAttachment { ByteBuffer receivedData; Vector<ByteBuffer> dataToSend = new Vector<ByteBuffer>(); SocketChannel socketChannel; boolean requestLengthHeaderRead; boolean valid = true; public UserChannelData(User owner, Object callingInstance) { super(owner, callingInstance); } public void clear() { synchronized (sync) { messageCounterReplies -= dataToSend.size(); } receivedData.clear(); dataToSend.clear(); try {socketChannel.close();} catch (IOException e) {} socketChannel = null; valid = false; } } // end UserChannelData private void removeUser(UserChannelData userData) { userData.clear(); synchronized (sync) { while(usersWithRepliesReady.remove(userData)); } numberOfActiveConnections--; userDatabase.removeUser(userData.getOwner(), this); selector.wakeup(); } private void openServerSocket() { try { serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); ServerSocket serverSocket = serverSocketChannel.socket(); InetSocketAddress endpoint = new InetSocketAddress(bindAddress, port); serverSocket.bind(endpoint, backlog); serverSocket.setSoTimeout(soTimeout); // generate selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println(anonNode +" listening on " +bindAddress +":" +port); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("could not open ServerSocket"); } } /** * Handles an accept request. Accepts connections until the maximum number * of connections is reached (see <code>numberOfActiveConnections</code>, * <code>maxConnections</code>). Generates <code>User</code> objects and * adds them to the <code>UserDatabase</code> (if connection accepted). * * @throws IOException If an I/O error occurres. */ private void handleAcceptRequest() throws IOException { if (numberOfActiveConnections < maxConnections) { SocketChannel clientSocketChannel = serverSocketChannel.accept(); clientSocketChannel.configureBlocking(false); numberOfActiveConnections++; User user = userDatabase.generateUser(); userDatabase.addUser(user, this); UserChannelData userData = new UserChannelData(user, this); userData.socketChannel = clientSocketChannel; clientSocketChannel.register(selector, SelectionKey.OP_READ, userData); } else { System.out.println(anonNode +" connection refused. too many connections ("+numberOfActiveConnections+")"); } } private Vector<Request> tryToReadRequests(UserChannelData userData) throws IOException { Vector<Request> requests = new Vector<Request>(); Request next = tryToReadRequest(userData); while (next != null) { requests.add(next); next = tryToReadRequest(userData); } return requests; } private Request tryToReadRequest(UserChannelData userData) throws IOException { if (userData.receivedData == null) userData.receivedData = ByteBuffer.allocate(4); // try to read header (containing the length of the message) if (!userData.requestLengthHeaderRead) { if (userData.receivedData.position() == 0) userData.receivedData.limit(4); if (userData.socketChannel.read(userData.receivedData) == -1) // read data throw new IOException("warning: lost connection to user " +userData.getOwner()); if (userData.receivedData.hasRemaining()) { return null; } else { userData.receivedData.flip(); byte[] lengthHeader = new byte[4]; userData.receivedData.get(lengthHeader); int messageLength = Util.byteArrayToInt(lengthHeader); if (messageLength > maxRequestLength) throw new IOException("warning: user " +userData.getOwner() +" sent a too large message"); userData.receivedData.clear(); userData.receivedData = ByteBuffer.allocate(messageLength); userData.requestLengthHeaderRead = true; } } // try to read the message itself assert userData.requestLengthHeaderRead == true; if (userData.socketChannel.read(userData.receivedData) == -1) // read data throw new IOException("warning: lost connection to user " +userData.getOwner()); if (userData.receivedData.hasRemaining()) { return null; } else { userData.receivedData.flip(); byte[] message = userData.receivedData.array(); userData.receivedData.clear(); userData.receivedData = null; userData.requestLengthHeaderRead = false; return MixMessage.getInstanceRequest(message, userData.getOwner()); } } private int tryToSendReplies(UserChannelData userData) throws IOException { int repliesSent = 0; while(tryToSendReply(userData)) repliesSent++; return repliesSent; } private boolean tryToSendReply(UserChannelData userData) throws IOException { if (userData.dataToSend.size() == 0) return false; ByteBuffer messageToSend = userData.dataToSend.get(0); int written = userData.socketChannel.write(messageToSend); assert written != 0; if (messageToSend.hasRemaining()) { return false; } else { userData.dataToSend.remove(0); return true; } } @Override public void userAdded(User user) { // no need to do anything } @Override public void userRemoved(User user) { UserChannelData userData = user.getAttachment(getThis(), UserChannelData.class); userData.clear(); synchronized (sync) { while(usersWithRepliesReady.remove(userData)); } numberOfActiveConnections--; selector.wakeup(); } private ClientHandler_TCP_FCFS_async_nio2 getThis() { return this; } }