This commit is contained in:
Ziver Koc 2008-11-14 16:38:36 +00:00
commit 613bef2496
108 changed files with 8397 additions and 0 deletions

View file

@ -0,0 +1,57 @@
package zutil.network.nio;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import zutil.network.nio.message.Message;
import zutil.network.nio.message.type.ResponseRequestMessage;
import zutil.network.nio.response.ResponseEvent;
public class NioClient extends NioNetwork{
private SocketChannel serverSocket;
/**
* Creates a NioClient that connects to a server
*
* @param hostAddress The server address
* @param port The port to listen on
*/
public NioClient(InetAddress serverAddress, int port) throws IOException {
super(InetAddress.getLocalHost(), port, NetworkType.CLIENT);
serverSocket = initiateConnection(new InetSocketAddress(serverAddress, port));
Thread thread = new Thread(this);
thread.setDaemon(false);
thread.start();
}
protected Selector initSelector() throws IOException {
// Create a new selector
return SelectorProvider.provider().openSelector();
}
/**
* Sends a Message to the default server
*
* @param data The data to be sent
* @throws IOException Something got wrong
*/
public void send(Message data) throws IOException {
send(serverSocket, data);
}
/**
* This method is for the Client to send a message to the server
*
* @param handler The response handler
* @param data The data to send
* @throws IOException
*/
public void send(ResponseEvent handler, ResponseRequestMessage data) throws IOException {
send(serverSocket, handler, data);
}
}

View file

@ -0,0 +1,433 @@
package zutil.network.nio;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
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.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import zutil.Converter;
import zutil.Encrypter;
import zutil.MultiPrintStream;
import zutil.network.nio.message.type.ResponseRequestMessage;
import zutil.network.nio.message.type.SystemMessage;
import zutil.network.nio.response.ResponseEvent;
import zutil.network.nio.server.ChangeRequest;
import zutil.network.nio.server.ClientData;
import zutil.network.nio.worker.SystemWorker;
import zutil.network.nio.worker.Worker;
public abstract class NioNetwork implements Runnable {
/**
* Debug level
* 0 = nothing
* 1 = connection debug
* 2 = message debug
* 3 = selector debug
*/
public static final int DEBUG = 1;
public static enum NetworkType {SERVER, CLIENT};
private NetworkType type;
// The host:port combination to listen on
protected InetAddress address;
protected int port;
// The channel on which we'll accept connections
protected ServerSocketChannel serverChannel;
// The selector we'll be monitoring
private Selector selector;
// The buffer into which we'll read data when it's available
private ByteBuffer readBuffer = ByteBuffer.allocate(8192);
protected Worker worker;
protected SystemWorker systemWorker;
// This map contains all the clients that are conncted
protected Map<InetSocketAddress, ClientData> clients = new HashMap<InetSocketAddress, ClientData>();
// A list of PendingChange instances
private List<ChangeRequest> pendingChanges = new LinkedList<ChangeRequest>();
// Maps a SocketChannel to a list of ByteBuffer instances
private Map<SocketChannel, List<ByteBuffer>> pendingData = new HashMap<SocketChannel, List<ByteBuffer>>();
// The encrypter
private Encrypter encrypter;
/**
* Create a nio network class
*
* @param hostAddress The host address
* @param port The port
* @param type The type of network host
* @throws IOException
*/
public NioNetwork(InetAddress address, int port, NetworkType type) throws IOException {
this.port = port;
this.address = address;
this.type = type;
this.selector = initSelector();
this.systemWorker = new SystemWorker(this);
}
protected abstract Selector initSelector() throws IOException;
/**
* Sets the Worker for the network messages
*
* @param worker The worker that handles the incoming messages
*/
public void setDefaultWorker(Worker worker){
this.worker = worker;
}
/**
* Sets the encrypter to use in the network
*
* @param enc The encrypter to use or null fo no encryption
*/
public void setEncrypter(Encrypter enc){
encrypter = enc;
MultiPrintStream.out.println("Network Encryption "+
(encrypter != null ? "Enabled("+encrypter.getAlgorithm()+")" : "Disabled")+"!!");
}
public void send(SocketChannel socket, Object data) {
send(socket, Converter.toBytes(data));
}
public void send(InetSocketAddress address, Object data){
send(address, Converter.toBytes(data));
}
public void send(InetSocketAddress address, byte[] data){
send(getSocketChannel(address), data);
}
public void send(SocketChannel socket, ResponseEvent handler, ResponseRequestMessage data) throws IOException {
// Register the response handler
systemWorker.addResponseHandler(handler, data);
queueSend(socket,Converter.toBytes(data));
}
/**
* This method sends data true the given socket
*
* @param socket The socket
* @param data The data to send
*/
public void send(SocketChannel socket, byte[] data) {
queueSend(socket,data);
}
/**
* Queues the message to be sent and wakeups the selector
*
* @param socket The socet to send the message thrue
* @param data The data to send
*/
protected void queueSend(SocketChannel socket, byte[] data){
if(DEBUG>=3)MultiPrintStream.out.println("Sending Queue...");
// And queue the data we want written
synchronized (pendingData) {
List<ByteBuffer> queue = pendingData.get(socket);
if (queue == null) {
queue = new ArrayList<ByteBuffer>();
pendingData.put(socket, queue);
}
//encrypts
if(encrypter != null)
queue.add(ByteBuffer.wrap(encrypter.encrypt(data)));
else queue.add(ByteBuffer.wrap(data));
}
// Changing the key state to write
synchronized (pendingChanges) {
// Indicate we want the interest ops set changed
pendingChanges.add(new ChangeRequest(socket, ChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE));
}
if(DEBUG>=3)MultiPrintStream.out.println("selector.wakeup();");
// Finally, wake up our selecting thread so it can make the required changes
selector.wakeup();
}
public void run() {
if(DEBUG>=1)MultiPrintStream.out.println("NioNetwork Started!!!");
while (true) {
try {
// Process any pending changes
synchronized (pendingChanges) {
Iterator<ChangeRequest> changes = pendingChanges.iterator();
while (changes.hasNext()) {
ChangeRequest change = changes.next();
switch (change.type) {
case ChangeRequest.CHANGEOPS:
SelectionKey key = change.socket.keyFor(selector);
key.interestOps(change.ops);
if(DEBUG>=3)MultiPrintStream.out.println("change.ops "+change.ops);
break;
case ChangeRequest.REGISTER:
change.socket.register(selector, change.ops);
if(DEBUG>=3)MultiPrintStream.out.println("register socket ");
break;
}
}
pendingChanges.clear();
}
// Wait for an event one of the registered channels
selector.select();
if(DEBUG>=3)MultiPrintStream.out.println("selector is awake");
// Iterate over the set of keys for which events are available
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = (SelectionKey) selectedKeys.next();
selectedKeys.remove();
if(DEBUG>=3)MultiPrintStream.out.println("KeyOP: "+key.interestOps()+" isAcceptable: "+SelectionKey.OP_ACCEPT+" isConnectable: "+SelectionKey.OP_CONNECT+" isWritable: "+SelectionKey.OP_WRITE+" isReadable: "+SelectionKey.OP_READ);
if (key.isValid()) {
// Check what event is available and deal with it
if (key.isAcceptable()) {
if(DEBUG>=3)MultiPrintStream.out.println("Accepting Connection!!");
accept(key);
}
else if (key.isConnectable()) {
if(DEBUG>=3)MultiPrintStream.out.println("Finnishing Connection!!");
finishConnection(key);
}
else if (key.isWritable()) {
if(DEBUG>=3)MultiPrintStream.out.println("Writing");
write(key);
}
else if (key.isReadable()) {
if(DEBUG>=3)MultiPrintStream.out.println("Reading");
read(key);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Server
*/
private void accept(SelectionKey key) throws IOException {
// For an accept to be pending the channel must be a server socket channel.
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// Accept the connection and make it non-blocking
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.socket().setReuseAddress(true);
socketChannel.configureBlocking(false);
// Register the new SocketChannel with our Selector, indicating
// we'd like to be notified when there's data waiting to be read
socketChannel.register(selector, SelectionKey.OP_READ);
// adds the client to the clients list
InetSocketAddress remoteAdr = (InetSocketAddress) socketChannel.socket().getRemoteSocketAddress();
if(!clients.containsValue(remoteAdr)){
clients.put(remoteAdr, new ClientData(socketChannel));
if(DEBUG>=1)MultiPrintStream.out.println("New Connection("+remoteAdr+")!!! Count: "+clients.size());
}
}
/**
* Client and Server
*/
private void read(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
InetSocketAddress remoteAdr = (InetSocketAddress) socketChannel.socket().getRemoteSocketAddress();
// Clear out our read buffer so it's ready for new data
readBuffer.clear();
// Attempt to read off the channel
int numRead;
try {
numRead = socketChannel.read(readBuffer);
} catch (IOException e) {
// The remote forcibly closed the connection, cancel
// the selection key and close the channel.
key.cancel();
socketChannel.close();
clients.remove(remoteAdr);
if(DEBUG>=1)MultiPrintStream.out.println("Connection Forced Close("+remoteAdr+")!!! Connection Count: "+clients.size());
if(type == NetworkType.CLIENT) throw new ConnectException("Server Closed The Connection!!!");
return;
}
if (numRead == -1) {
// Remote entity shut the socket down cleanly. Do the
// same from our end and cancel the channel.
key.channel().close();
key.cancel();
clients.remove(remoteAdr);
if(DEBUG>=1)MultiPrintStream.out.println("Connection Close("+remoteAdr+")!!! Connection Count: "+clients.size());
if(type == NetworkType.CLIENT) throw new ConnectException("Server Closed The Connection!!!");
return;
}
// Make a correctly sized copy of the data before handing it
// to the client
byte[] rspByteData = new byte[numRead];
System.arraycopy(readBuffer.array(), 0, rspByteData, 0, numRead);
handleRecivedMessage(socketChannel, rspByteData);
}
/**
* Client and Server
*/
private void write(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
synchronized (pendingData) {
List<ByteBuffer> queue = pendingData.get(socketChannel);
if(queue == null){
queue = new ArrayList<ByteBuffer>();
}
// Write until there's not more data ...
while (!queue.isEmpty()) {
ByteBuffer buf = queue.get(0);
socketChannel.write(buf);
if (buf.remaining() > 0) {
// ... or the socket's buffer fills up
break;
}
queue.remove(0);
}
if (queue.isEmpty()) {
// We wrote away all data, so we're no longer interested
// in writing on this socket. Switch back to waiting for
// data.
if(DEBUG>=3)MultiPrintStream.out.println("No more Data to write!!");
key.interestOps(SelectionKey.OP_READ);
}
}
}
private void handleRecivedMessage(SocketChannel socketChannel, byte[] rspByteData){
//Encryption
Object rspData;
if(encrypter != null)
rspData = Converter.toObject(encrypter.decrypt(rspByteData));
else rspData = Converter.toObject(rspByteData);
if(DEBUG>=2)MultiPrintStream.out.println("Handling incomming message...");
if(rspData instanceof SystemMessage){
if(systemWorker != null){
if(DEBUG>=3)MultiPrintStream.out.println("System Message!!!");
systemWorker.processData(this, socketChannel, rspData);
}
else{
if(DEBUG>=2)MultiPrintStream.out.println("Unhandled System Message!!!");
}
}
else{
// Hand the data off to our worker thread
if(worker != null){
if(DEBUG>=3)MultiPrintStream.out.println("Worker Message!!!");
worker.processData(this, socketChannel, rspData);
}
else{
if(DEBUG>=1)MultiPrintStream.out.println("Unhandled Message!!!");
}
}
}
/**
* Initializes a socket to the server
*/
protected SocketChannel initiateConnection(InetSocketAddress address) throws IOException {
// Create a non-blocking socket channel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.socket().setReuseAddress(true);
socketChannel.configureBlocking(false);
if(DEBUG>=1)MultiPrintStream.out.println("Connecting to: "+address);
// Kick off connection establishment
socketChannel.connect(address);
// Queue a channel registration since the caller is not the
// selecting thread. As part of the registration we'll register
// an interest in connection events. These are raised when a channel
// is ready to complete connection establishment.
synchronized(this.pendingChanges) {
pendingChanges.add(new ChangeRequest(socketChannel, ChangeRequest.REGISTER, SelectionKey.OP_CONNECT));
}
return socketChannel;
}
protected SocketChannel getSocketChannel(InetSocketAddress address){
return clients.get(address).getSocketChannel();
}
/**
* Client
*/
private void finishConnection(SelectionKey key){
SocketChannel socketChannel = (SocketChannel) key.channel();
// Finish the connection. If the connection operation failed
// this will raise an IOException.
try {
socketChannel.finishConnect();
} catch (IOException e) {
// Cancel the channel's registration with our selector
e.printStackTrace();
key.cancel();
return;
}
// Register an interest in writing on this channel
key.interestOps(SelectionKey.OP_WRITE);
}
/**
* Client
* @throws IOException
*/
protected void closeConnection(SocketChannel socketChannel) throws IOException{
socketChannel.close();
socketChannel.keyFor(selector).cancel();
}
/**
* Client
* @throws IOException
*/
protected void closeConnection(InetSocketAddress address) throws IOException{
closeConnection(getSocketChannel(address));
}
/*
public void close() throws IOException{
if(serverChannel != null){
serverChannel.close();
serverChannel.keyFor(selector).cancel();
}
selector.close();
}*/
public NetworkType getType(){
return type;
}
}

View file

@ -0,0 +1,68 @@
package zutil.network.nio;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
public class NioServer extends NioNetwork{
/**
* Creates a NioServer object which listens on localhost
*
* @param port The port to listen to
*/
public NioServer(int port) throws IOException {
this(null, port);
}
/**
* Creates a NioServer object which listens to a specific address
*
* @param address The address to listen to
* @param port The port to listen to
*/
public NioServer(InetAddress address, int port) throws IOException {
super(address, port, NetworkType.SERVER);
new Thread(this).start();
}
protected Selector initSelector() throws IOException {
// Create a new selector
Selector socketSelector = SelectorProvider.provider().openSelector();
// Create a new non-blocking server socket channel
serverChannel = ServerSocketChannel.open();
serverChannel.socket().setReuseAddress(true);
serverChannel.configureBlocking(false);
// Bind the server socket to the specified address and port
InetSocketAddress isa = new InetSocketAddress(address, port);
serverChannel.socket().bind(isa);
// Register the server socket channel, indicating an interest in
// accepting new connections
serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);
return socketSelector;
}
/**
* Broadcasts the message to all the connected clients
*
* @param data The data to broadcast
*/
public void broadcast(byte[] data){
synchronized(clients){
Iterator<InetSocketAddress> it = clients.keySet().iterator();
while(it.hasNext()){
send(it.next(), data);
}
}
}
}

View file

@ -0,0 +1,45 @@
package zutil.network.nio.message;
public class ChatMessage extends Message{
private static final long serialVersionUID = 1L;
public static enum ChatMessageType {REGISTER, UNREGISTER, MESSAGE};
public ChatMessageType type;
public String msg;
public String room;
/**
* Registers the user to the main chat
*
* @param name Name of user
*/
public ChatMessage(){
this("", "", ChatMessageType.REGISTER);
}
/**
* Registers the user to the given room
*
* @param room The room to register to
*/
public ChatMessage(String room){
this("", room, ChatMessageType.REGISTER);
}
/**
* Sends a message to the given room
*
* @param msg The message
* @param room The room
*/
public ChatMessage(String msg, String room){
this(msg, room, ChatMessageType.MESSAGE);
}
public ChatMessage(String msg, String room, ChatMessageType type){
this.msg = msg;
this.room = room;
this.type = type;
}
}

View file

@ -0,0 +1,31 @@
package zutil.network.nio.message;
public class GraphicsSyncMessage extends SyncMessage{
private static final long serialVersionUID = 1L;
public float locX;
public float locY;
public float locZ;
public float rotX;
public float rotY;
public float rotZ;
public float rotW;
public GraphicsSyncMessage(String id){
this.type = MessageType.SYNC;
this.id = id;
}
public boolean equals(Object obj){
if(obj instanceof GraphicsSyncMessage){
GraphicsSyncMessage tmp = (GraphicsSyncMessage)obj;
return (tmp.locX == locX && tmp.locY == locY &&
tmp.locZ == locZ && tmp.rotX == rotX &&
tmp.rotY == rotY && tmp.rotZ == rotZ &&
tmp.rotW == rotW);
}
return false;
}
}

View file

@ -0,0 +1,16 @@
package zutil.network.nio.message;
import zutil.network.nio.message.type.SystemMessage;
/**
* Tells the destination that the
* source is still online
*
* @author Ziver
*
*/
public class KeepAliveMessage extends Message implements SystemMessage{
private static final long serialVersionUID = 1L;
}

View file

@ -0,0 +1,9 @@
package zutil.network.nio.message;
import java.io.Serializable;
public class Message implements Serializable{
private static final long serialVersionUID = 1L;
}

View file

@ -0,0 +1,34 @@
package zutil.network.nio.message;
import zutil.network.nio.message.type.EchoMessage;
import zutil.network.nio.message.type.ResponseRequestMessage;
public class StringMessage extends EchoMessage implements ResponseRequestMessage{
private static final long serialVersionUID = 1L;
private double responseId;
private String msg;
public StringMessage(String msg){
this.msg = msg;
responseId = Math.random();
}
public String getString(){
return msg;
}
public void setString(String msg){
this.msg = msg;
}
public String toString(){
return getString();
}
public double getResponseId() {
return responseId;
}
}

View file

@ -0,0 +1,13 @@
package zutil.network.nio.message;
import zutil.network.nio.message.type.SystemMessage;
public class SyncMessage extends Message implements SystemMessage{
private static final long serialVersionUID = 1L;
public static enum MessageType { REQUEST_ID, NEW, REMOVE, SYNC };
// type of message
public MessageType type;
// id of the Object
public String id;
}

View file

@ -0,0 +1,34 @@
package zutil.network.nio.message.type;
import zutil.network.nio.message.Message;
/**
* The reciver will echo out this message to the sender
*
* @author Ziver
*/
public abstract class EchoMessage extends Message implements SystemMessage{
private static final long serialVersionUID = 1L;
private boolean echo;
public EchoMessage(){
echo = true;
}
/**
* This method returns if the message should be echoed
* @return If the message should be echoed
*/
public boolean echo() {
return echo;
}
/**
* Called by the reciver to disable looping of the message
*
*/
public void recived() {
echo = false;
}
}

View file

@ -0,0 +1,18 @@
package zutil.network.nio.message.type;
/**
* This interface means that the sender
* wants a reply from the destination
*
* @author Ziver
*
*/
public interface ResponseRequestMessage {
/**
* The id of the response to identify the response event
* @return Response id
*/
public double getResponseId();
}

View file

@ -0,0 +1,12 @@
package zutil.network.nio.message.type;
/**
* A message that implements this will be
* handeld internaly by the network engine
*
* @author Ziver
*
*/
public interface SystemMessage {
}

View file

@ -0,0 +1,12 @@
package zutil.network.nio.response;
import zutil.MultiPrintStream;
public class PrintRsp extends ResponseEvent{
@Override
protected void responseEvent(Object rsp) {
MultiPrintStream.out.println(rsp);
}
}

View file

@ -0,0 +1,43 @@
package zutil.network.nio.response;
public abstract class ResponseEvent {
private Object rsp = null;
public synchronized boolean handleResponse(Object rsp) {
this.rsp = rsp;
notify();
return true;
}
/**
* Blocks the Thread until there is a response
*/
public synchronized void waitForResponse() {
while(!gotResponse()) {
try {
this.wait();
} catch (InterruptedException e) {}
}
responseEvent(rsp);
}
/**
* Handles the response
*/
public void handleResponse(){
if(gotResponse()){
responseEvent(rsp);
}
}
/**
* @return If there is an response
*/
public boolean gotResponse(){
return (rsp != null);
}
protected abstract void responseEvent(Object rsp);
}

View file

@ -0,0 +1,41 @@
package zutil.network.nio.response;
import java.util.LinkedList;
import java.util.List;
public abstract class ResponseHandler implements Runnable{
private List<ResponseEvent> queue = new LinkedList<ResponseEvent>();
public ResponseHandler(){
}
public synchronized void addResponseEvent(ResponseEvent re){
queue.add(re);
notify();
}
public synchronized void removeResponseEvent(ResponseEvent re){
queue.remove(re);
}
public void run() {
while(true) {
try {
this.wait();
} catch (InterruptedException e) {}
update();
}
}
public synchronized void update(){
while(!queue.isEmpty()){
queue.get(0).handleResponse();
if(queue.get(0).gotResponse()){
queue.remove(0);
}
}
}
}

View file

@ -0,0 +1,18 @@
package zutil.network.nio.server;
import java.nio.channels.SocketChannel;
public class ChangeRequest {
public static final int REGISTER = 1;
public static final int CHANGEOPS = 2;
public SocketChannel socket;
public int type;
public int ops;
public ChangeRequest(SocketChannel socket, int type, int ops) {
this.socket = socket;
this.type = type;
this.ops = ops;
}
}

View file

@ -0,0 +1,29 @@
package zutil.network.nio.server;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
public class ClientData {
private SocketChannel socketChannel;
private long lastMessageReceived;
public ClientData(SocketChannel socketChannel){
this.socketChannel = socketChannel;
}
public SocketChannel getSocketChannel(){
return socketChannel;
}
public InetSocketAddress getAddress(){
return (InetSocketAddress) socketChannel.socket().getRemoteSocketAddress();
}
public void setLastMessageReceived(long time){
lastMessageReceived = time;
}
public long getLastMessageReceived(){
return lastMessageReceived;
}
}

View file

@ -0,0 +1,10 @@
package zutil.network.nio.service;
/**
* Tis is a listener class for new chat messages
* @author Ziver
*
*/
public interface ChatListener {
public void messageAction(String msg, String room);
}

View file

@ -0,0 +1,121 @@
package zutil.network.nio.service;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.LinkedList;
import zutil.MultiPrintStream;
import zutil.network.nio.NioNetwork;
import zutil.network.nio.message.ChatMessage;
import zutil.network.nio.message.Message;
public class ChatService extends NetworkService{
private HashMap<String,LinkedList<SocketChannel>> rooms;
private ChatListener listener;
public ChatService(NioNetwork nio){
super(nio);
rooms = new HashMap<String,LinkedList<SocketChannel>>();
}
@Override
public void handleMessage(Message message, SocketChannel socket) {
// New message
if(message instanceof ChatMessage){
ChatMessage chatmessage = (ChatMessage)message;
//is this a new message
if(chatmessage.type == ChatMessage.ChatMessageType.MESSAGE){
// Is this the server
if(nio.getType() == NioNetwork.NetworkType.SERVER){
if(rooms.containsKey(chatmessage.room)){
LinkedList<SocketChannel> tmpList = rooms.get(chatmessage.room);
// Broadcast the message
for(SocketChannel s : tmpList){
if(s.isConnected()){
nio.send(s, chatmessage);
}
else{
unRegisterUser(chatmessage.room, s);
}
}
}
}
if(NioNetwork.DEBUG>=2)MultiPrintStream.out.println("New Chat Message: "+chatmessage.msg);
listener.messageAction(chatmessage.msg, chatmessage.room);
}
// register to a room
else if(chatmessage.type == ChatMessage.ChatMessageType.REGISTER){
registerUser(chatmessage.room, socket);
}
// unregister to a room
else if(chatmessage.type == ChatMessage.ChatMessageType.UNREGISTER){
unRegisterUser(chatmessage.room, socket);
}
}
}
/**
* Registers a user to the main room
*
* @param socket The socket to the user
*/
public void registerUser(SocketChannel socket){
registerUser("", socket);
}
/**
* Registers the given user to a specific room
*
* @param room The room
* @param socket The socket to the user
*/
public void registerUser(String room, SocketChannel socket){
addRoom(room);
if(NioNetwork.DEBUG>=1)MultiPrintStream.out.println("New Chat User: "+socket);
rooms.get(room).add(socket);
}
/**
* Unregisters a user from a room and removes the room if its empty
*
* @param room The room
* @param socket The socket to the user
*/
public void unRegisterUser(String room, SocketChannel socket){
if(rooms.containsKey(room)){
if(NioNetwork.DEBUG>=1)MultiPrintStream.out.println("Remove Chat User: "+socket);
rooms.get(room).remove(socket);
removeRoom(room);
}
}
/**
* Adds a room into the list
*
* @param room The name of the room
*/
public void addRoom(String room){
if(!rooms.containsKey(room)){
if(NioNetwork.DEBUG>=1)MultiPrintStream.out.println("New Chat Room: "+room);
rooms.put(room, new LinkedList<SocketChannel>());
}
}
/**
* Removes the given room if its empty
*
* @param room The room
*/
public void removeRoom(String room){
if(rooms.get(room).isEmpty()){
if(NioNetwork.DEBUG>=1)MultiPrintStream.out.println("Remove Chat Room: "+room);
rooms.remove(room);
}
}
public static ChatService getInstance(){
return (ChatService)instance;
}
}

View file

@ -0,0 +1,25 @@
package zutil.network.nio.service;
import java.nio.channels.SocketChannel;
import zutil.network.nio.NioNetwork;
import zutil.network.nio.message.Message;
public abstract class NetworkService {
protected static NetworkService instance;
protected NioNetwork nio;
public NetworkService(NioNetwork nio){
instance = this;
this.nio = nio;
}
public abstract void handleMessage(Message message, SocketChannel socket);
/**
* @return A instance of this class
*/
public static NetworkService getInstance(){
return instance;
}
}

View file

@ -0,0 +1,28 @@
package zutil.network.nio.service.sync;
import zutil.network.nio.message.SyncMessage;
public abstract class ObjectSync {
public String id;
public ObjectSync(String id){
this.id = id;
}
/**
* Sends sync message if the object has bean changed
*/
public abstract void sendSync();
/**
* Applies the SyncMessage to the object
* @param message
* @param object
*/
public abstract void syncObject(SyncMessage message);
/**
* Called when the object is removed from the sync list
*/
public abstract void remove();
}

View file

@ -0,0 +1,58 @@
package zutil.network.nio.service.sync;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import zutil.MultiPrintStream;
import zutil.network.nio.NioNetwork;
import zutil.network.nio.message.Message;
import zutil.network.nio.message.SyncMessage;
import zutil.network.nio.service.NetworkService;
public class SyncService extends NetworkService{
// list of objects to sync
private HashMap<String, ObjectSync> sync;
public SyncService(NioNetwork nio){
super(nio);
sync = new HashMap<String, ObjectSync>();
}
/**
* Adds a SyncObject to the sync list
* @param os The object to sync
*/
public void addSyncObject(ObjectSync os){
sync.put(os.id, os);
if(NioNetwork.DEBUG>=1)MultiPrintStream.out.println("New Sync object: "+os);
}
public void handleMessage(Message message, SocketChannel socket){
if(message instanceof SyncMessage){
SyncMessage syncMessage = (SyncMessage)message;
if(syncMessage.type == SyncMessage.MessageType.SYNC){
ObjectSync obj = sync.get(syncMessage.id);
if(obj != null){
if(NioNetwork.DEBUG>=2)MultiPrintStream.out.println("Syncing Message...");
obj.syncObject(syncMessage);
}
}
else if(syncMessage.type == SyncMessage.MessageType.REMOVE){
sync.remove(syncMessage.id).remove();
}
}
}
/**
* Syncs all the objects whit the server
*/
public void sync(){
for(String id : sync.keySet()){
sync.get(id).sendSync();
}
}
public static SyncService getInstance(){
return (SyncService)instance;
}
}

View file

@ -0,0 +1,16 @@
package zutil.network.nio.worker;
import zutil.MultiPrintStream;
public class EchoWorker extends ThreadedEventWorker {
@Override
public void messageEvent(WorkerDataEvent dataEvent) {
// Return to sender
MultiPrintStream.out.println("Recived Msg: "+dataEvent.data);
dataEvent.network.send(dataEvent.socket, dataEvent.data);
}
}

View file

@ -0,0 +1,126 @@
package zutil.network.nio.worker;
import java.util.HashMap;
import java.util.Map;
import zutil.MultiPrintStream;
import zutil.network.nio.NioNetwork;
import zutil.network.nio.message.ChatMessage;
import zutil.network.nio.message.Message;
import zutil.network.nio.message.SyncMessage;
import zutil.network.nio.message.type.EchoMessage;
import zutil.network.nio.message.type.ResponseRequestMessage;
import zutil.network.nio.response.ResponseEvent;
import zutil.network.nio.service.ChatService;
import zutil.network.nio.service.NetworkService;
import zutil.network.nio.service.sync.SyncService;
public class SystemWorker extends ThreadedEventWorker {
private NioNetwork nio;
// Maps a SocketChannel to a RspHandler
private Map<Double, ResponseEvent> rspEvents = new HashMap<Double, ResponseEvent>();
// Difren services listening on specific messages
@SuppressWarnings("unchecked")
private Map<Class, NetworkService> services = new HashMap<Class, NetworkService>();
/**
* Creates a new SystemWorker
* @param nio The Network
*/
public SystemWorker(NioNetwork nio){
this.nio = nio;
}
@Override
public void messageEvent(WorkerDataEvent event) {
if(NioNetwork.DEBUG>=2)MultiPrintStream.out.println("System Message: "+event.data.getClass().getName());
if(event.data instanceof Message){
if(event.data instanceof EchoMessage && ((EchoMessage)event.data).echo()){
// Echos back the recived message
((EchoMessage)event.data).recived();
if(NioNetwork.DEBUG>=3)MultiPrintStream.out.println("Echoing Message: "+event.data);
nio.send(event.socket, event.data);
}
else if(event.data instanceof ResponseRequestMessage &&
rspEvents.get(((ResponseRequestMessage)event.data).getResponseId()) != null){
// Handle the response
handleResponse(((ResponseRequestMessage)event.data).getResponseId(), event.data);
if(NioNetwork.DEBUG>=3)MultiPrintStream.out.println("Response Request Message: "+event.data);
}
else{
//Services
if(services.containsKey(event.data.getClass()) ||
!services.containsKey(event.data.getClass()) && defaultServices(event.data)){
services.get(event.data.getClass()).handleMessage((Message)event.data, event.socket);
}
}
}
}
/**
* Registers a Service to a specific message
*
* @param c The Message class
* @param ns The service
*/
@SuppressWarnings("unchecked")
public void registerService(Class c, NetworkService ns){
services.put(c, ns);
}
/**
* Unregisters a service
*
* @param c The class
*/
@SuppressWarnings("unchecked")
public void unregisterService(Class c){
services.remove(c);
}
/**
* Connects a ResponseHandler to a specific message
* @param handler The Handler
* @param data The Message
*/
public void addResponseHandler(ResponseEvent handler, ResponseRequestMessage data){
rspEvents.put(data.getResponseId(), handler);
}
/**
* Client And Server ResponseEvent
*/
private void handleResponse(double responseId, Object rspData){
// Look up the handler for this channel
ResponseEvent handler = rspEvents.get(responseId);
// And pass the response to it
handler.handleResponse(rspData);
rspEvents.remove(responseId);
}
/**
* Registers the default services in the engin e
* if the message needs one of them
*
* @param o The message
*/
private boolean defaultServices(Object o){
if(o instanceof SyncMessage){
if(SyncService.getInstance() == null)
registerService(o.getClass(), new SyncService(nio));
else
registerService(o.getClass(), SyncService.getInstance());
return true;
}
else if(o instanceof ChatMessage){
if(ChatService.getInstance() == null)
registerService(o.getClass(), new ChatService(nio));
else
registerService(o.getClass(), ChatService.getInstance());
return true;
}
return false;
}
}

View file

@ -0,0 +1,34 @@
package zutil.network.nio.worker;
public abstract class ThreadedEventWorker extends Worker{
private Thread thread;
public ThreadedEventWorker(){
thread = new Thread(this);
thread.start();
}
public void update() {
WorkerDataEvent dataEvent;
while(true) {
try{
// Wait for data to become available
synchronized(getEventQueue()) {
while(getEventQueue().isEmpty()) {
try {
getEventQueue().wait();
} catch (InterruptedException e) {}
}
dataEvent = (WorkerDataEvent) getEventQueue().remove(0);
}
messageEvent(dataEvent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public abstract void messageEvent(WorkerDataEvent e);
}

View file

@ -0,0 +1,52 @@
package zutil.network.nio.worker;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.List;
import zutil.network.nio.NioNetwork;
public abstract class Worker implements Runnable {
private LinkedList<WorkerDataEvent> queue = new LinkedList<WorkerDataEvent>();
public void processData(NioNetwork server, SocketChannel socket, Object data) {
synchronized(queue) {
queue.add(new WorkerDataEvent(server, socket, data));
queue.notify();
}
}
/**
* @return The event queue
*/
protected List<WorkerDataEvent> getEventQueue(){
return queue;
}
/**
* @return If there is a event in the queue
*/
protected boolean hasEvent(){
return !queue.isEmpty();
}
/**
* Polls a event from the list or waits until there is a event
* @return The next event
*/
protected WorkerDataEvent pollEvent(){
while(queue.isEmpty()) {
try {
this.wait();
} catch (InterruptedException e) {}
}
return queue.poll();
}
public void run(){
update();
}
public abstract void update();
}

View file

@ -0,0 +1,18 @@
package zutil.network.nio.worker;
import java.nio.channels.SocketChannel;
import zutil.network.nio.NioNetwork;
public class WorkerDataEvent {
public NioNetwork network;
public SocketChannel socket;
public Object data;
public WorkerDataEvent(NioNetwork server, SocketChannel socket, Object data) {
this.network = server;
this.socket = socket;
this.data = data;
}
}