/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2014, Telestax Inc and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
package org.restcomm.media.network.deprecated.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import org.apache.log4j.Logger;
import org.restcomm.media.network.deprecated.channel.PacketHandler;
import org.restcomm.media.network.deprecated.channel.PacketHandlerException;
import org.restcomm.media.network.deprecated.channel.PacketHandlerPipeline;
/**
* Non-blocking, single-threaded server that relies on a pipeline of handlers to
* process incoming packets.
*
* @author Henrique Rosa (henrique.rosa@telestax.com)
*
*/
public class NioServer {
private static final Logger logger = Logger.getLogger(NioServer.class);
private static final int MAX_BUFFER_SIZE = 8192;
private final ByteBuffer buffer;
private final PacketHandlerPipeline packetHandlers;
private boolean running;
protected DatagramChannel currentChannel;
private Selector selector;
private final Worker worker;
private Thread workerThread;
public NioServer() {
this.buffer = ByteBuffer.allocateDirect(MAX_BUFFER_SIZE);
this.packetHandlers = new PacketHandlerPipeline();
this.worker = new Worker();
this.running = false;
}
/**
* Indicates whether the server is running or not.
*
* @return Returns true if the server is currently running. Returns false
* otherwise.
*/
public boolean isRunning() {
return running;
}
/**
* Registers a packet handler in the pipeline.
*
* @param handler
* The packet handler to be registered.
* @return Returns true if the handler was successfully registered. Returns
* false otherwise.
*/
public boolean addPacketHandler(PacketHandler handler) {
return this.packetHandlers.addHandler(handler);
}
private byte[] getBufferedData(int dataLength) {
byte[] data = new byte[dataLength];
this.buffer.rewind();
this.buffer.get(data, 0, dataLength);
return data;
}
private int receive(DatagramChannel channel, ByteBuffer buffer) {
int dataLength = 0;
try {
buffer.clear();
SocketAddress remotePeer = channel.receive(buffer);
if(!channel.isConnected() && remotePeer != null) {
channel.connect(remotePeer);
}
dataLength = this.buffer.position();
} catch (IOException e) {
logger.error("Could not receive data: "+ e.getMessage(), e);
dataLength = -1;
}
return dataLength;
}
private int send(DatagramChannel channel, byte[] data, ByteBuffer buffer) {
// Prepare buffer
buffer.clear();
buffer.put(data).flip();
// Send data to remote peer
int dataLength = 0;
try {
dataLength = channel.send(buffer, channel.getRemoteAddress());
} catch (IOException e) {
logger.error("Could not send data: "+ e.getMessage(), e);
dataLength = -1;
}
return dataLength;
}
public void start(final Selector selector) {
if(!this.running) {
logger.info("Started NIO Server");
this.running = true;
this.selector = selector;
this.workerThread = new Thread(this.worker);
this.workerThread.start();
}
}
public void stop() {
if(this.running) {
logger.info("Stopping NIO Server...");
this.running = false;
}
}
private class Worker implements Runnable {
public void run() {
while(running && selector.isOpen()) {
try {
// select channels ready for IO
selector.selectNow();
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
// receive data from underlying channel
currentChannel = (DatagramChannel) key.channel();
int dataLength = receive(currentChannel, buffer);
/*
* If an error occurred, close the channel and continue iterating.
* If data was received, process incoming packet.
* If no data was received, leave channel open and skip to next key.
*/
if(dataLength < 0) {
try {
currentChannel.close();
} catch (IOException e) {
logger.error("Could not close defective channel: "+ e.getMessage(), e);
}
continue;
} else if(dataLength > 0) {
// Find proper handler to process incoming packet
byte[] data = getBufferedData(dataLength);
PacketHandler handler = packetHandlers.getHandler(data);
if(handler != null) {
try {
// Process incoming packet and send response back, if any
byte[] response = handler.handle(data, (InetSocketAddress) currentChannel.getLocalAddress(), (InetSocketAddress) currentChannel.getRemoteAddress());
if(response != null && key.isWritable()) {
send(currentChannel, response, buffer);
}
} catch (PacketHandlerException e) {
logger.error("Could not process incoming packet. Packet will be dropped.");
}
} else {
logger.warn("No handler found to process incoming packet. Packet will be dropped.");
}
}
}
} catch (IOException e) {
logger.error("Could not select keys: "+ e.getMessage(), e);
}
}
if(selector.isOpen()) {
try {
selector.close();
} catch (IOException e) {
logger.error("Could not close selector: "+ e.getMessage(), e);
}
}
currentChannel = null;
selector = null;
logger.info("NIO Server stopped");
}
}
}