/*
* ServerConnection.java
*
* Created on June 22, 2011
*************************************************************************
* Copyright 2011 Kevin Kendall
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ao.chat;
import ao.event.EventListenerList;
import ao.protocol.ServerListener;
import ao.protocol.ServerLogger;
import ao.protocol.ServerStateException;
import ao.protocol.packets.Packet;
import ao.protocol.packets.bi.PingPacket;
import ao.protocol.packets.utils.PacketFactory;
import ao.protocol.packets.utils.SimplePacketFactory;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
/**
*
* @author SITS_13
*/
public class ServerConnection implements Runnable {
private PacketFactory m_packetFactory;
private Socket m_socket;
private ServerState m_state = ServerState.DISCONNECTED;
private boolean m_debug;
//Connection
private DataOutputStream m_out;
private DataInputStream m_in;
private boolean timingout = false;
//Events
private final EventListenerList m_listeners = new EventListenerList();
// used for synchronization
private final Object m_readLock = new Object();
private final Object m_writeLock = new Object();
private final Object m_stateLock = new Object();
private final Object m_timeoutLock = new Object();
public enum ServerState {
DISCONNECTED, CONNECTED;
} // end enum State
public ServerConnection(Socket socket, boolean debug) {
m_socket = socket;
m_debug = debug;
m_packetFactory = new SimplePacketFactory();
}
public ServerState getState() {
return m_state;
}
public void run() {
try {
m_out = new DataOutputStream(m_socket.getOutputStream());
m_in = new DataInputStream(m_socket.getInputStream());
m_state = ServerState.CONNECTED;
} catch (IOException ex) {
m_state = ServerState.DISCONNECTED;
fireException(ex);
}
while (getState() != ServerState.DISCONNECTED) {
try {
Packet packet = nextPacket();
if (packet instanceof PingPacket) {
PingPacket ping = (PingPacket) packet;
synchronized (m_timeoutLock) {
if (ping.getDirection() == Packet.Direction.TO_CLIENT) {
timingout = false;
}
}
}
firePacket(packet);
} catch (IOException ex) {
fireException(ex);
}
Thread.yield();
}
}
public Packet nextPacket() throws IOException {
synchronized (m_readLock) {
try {
// Read and parse a packet from the input stream
short type = m_in.readShort();
short length = m_in.readShort();
byte[] data = new byte[length];
m_in.readFully(data);
Packet packet = m_packetFactory.toPacket(type, data);
// DEBUG: display that a packet was recieved
if (m_debug) {
System.out.println("IN: " + packet);
}
// Return the packet
return packet;
} catch (SocketTimeoutException ex) {
if (m_state != ServerState.DISCONNECTED) {
synchronized (m_timeoutLock) {
if (!timingout) {
timingout = true;
// Send a ping
sendPacket(new PingPacket("Java AOChat API ping", Packet.Direction.TO_SERVER));
// Read a ping
Packet packet = nextPacket();
return packet;
} else {
synchronized (m_stateLock) {
if (m_socket == null || m_socket.isClosed()) {
println("Connection Lost...");
disconnect();
return null;
} else {
println("Connection Lost...");
disconnect();
throw ex;
} // end else
} // end synchronized
}
}
} else {
return null;
}
} catch (SocketException ex) {
if (m_socket == null || m_socket.isClosed()) {
println("Connection Lost...");
disconnect();
return null;
} else {
println("Connection Lost...");
disconnect();
throw ex;
} // end else
} catch (EOFException ex) {
if (m_socket == null || m_socket.isClosed()) {
println("Connection Lost...");
disconnect();
return null;
} else {
println("Connection Lost...");
disconnect();
throw ex;
} // end else
} // end catch
} // end else
} // end nextPacket()
public void sendPacket(Packet packet) throws IOException {
synchronized (m_writeLock) {
if (m_state == ServerState.DISCONNECTED) {
throw new ServerStateException(
"The client is not currently connected to the server. It must be connected before packets can be sent.",
m_state, ServerState.CONNECTED);
} else {
try {
short type = packet.getType();
byte[] data = packet.getData();
m_out.writeShort(type);
m_out.writeShort(data.length);
m_out.write(data, 0, data.length);
m_out.flush();
// DEBUG: display that a packet was sent
if (m_debug) {
System.out.println("OUT: " + packet);
}
} catch (SocketException ex) {
if (m_socket.isClosed()) {
println("Connection Lost...");
disconnect();
return;
} else {
throw ex;
} // end else
} // end catch
}
} // end synchronized
} // end sendPacket()
public void disconnect() throws IOException {
synchronized (m_stateLock) {
m_state = ServerState.DISCONNECTED;
m_socket.close();
}
// Encourage garbage collection
System.runFinalization();
System.gc();
println("Disconnected");
fireDisconnected();
}
public void addListener(ServerListener listener) {
m_listeners.add(ServerListener.class, listener);
}
public void removeListener(ServerListener l) {
m_listeners.remove(ServerListener.class, l);
} // end removeListener()
protected void fireDisconnected() {
ServerListener[] listeners = m_listeners.getListeners(ServerListener.class);
for (ServerListener l : listeners) {
l.disconnected(this);
} // end for
} // end fireDisconnected()
protected void firePacket(Packet packet) {
ServerListener[] listeners = m_listeners.getListeners(ServerListener.class);
for (ServerListener l : listeners) {
l.packet(this, packet);
} // end for
}
protected void fireException(Exception e) {
ServerListener[] listeners = m_listeners.getListeners(ServerListener.class);
for (ServerListener l : listeners) {
l.exception(this, e);
} // end for
}
protected void println(String msg) {
ServerLogger[] listeners = m_listeners.getListeners(ServerLogger.class);
for (ServerLogger l : listeners) {
l.print(this, msg);
} // end for
} // end println()
}