/*
* Copyright 2011 Uwe Krueger.
*
* 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 com.mandelsoft.mand.srv.tcp;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.mandelsoft.mand.MandelFileName;
import com.mandelsoft.mand.srv.AbstractServer;
import com.mandelsoft.mand.srv.CalcRequest;
import com.mandelsoft.mand.srv.ImageData;
import com.mandelsoft.util.Queue;
/**
*
* @author Uwe Krüger
*/
public class Server extends AbstractServer implements Constants, Runnable {
private ServerSocket socket;
private volatile Queue<CalcRequest> requests;
private volatile Queue<CalcRequest> done;
private volatile ActiveList active;
private Handler handler;
private Timeout timeout;
private Thread server;
private volatile StatisticHandler stat;
private boolean log=false;
private boolean verb=true;
public Server(String[] args) throws IOException
{
int port=PORT;
int c=0;
while (args.length>c&&args[c].charAt(0)=='-') {
String arg=args[c++];
for (int i=1; i<arg.length(); i++) {
char opt;
switch (opt=arg.charAt(i)) {
case 'p': // port
if (args.length>c) {
try {
port=Integer.parseInt(args[c++]);
}
catch (Exception ex) {
throw new IllegalArgumentException("port number expected");
}
}
else throw new IllegalArgumentException("port missing");
break;
default:
throw new IllegalArgumentException("illegal option '"+opt+"'");
}
}
}
setup(true,port);
}
public Server(boolean run) throws IOException
{
setup(run,PORT);
}
public Server(boolean run, int port) throws IOException
{
setup(run,port);
}
private void setup(boolean run,int port) throws IOException
{
stat=new StatisticHandler(20);
requests=new Queue<CalcRequest>();
done=new Queue<CalcRequest>();
active=new ActiveList();
System.out.println("starting server at "+port);
socket=new ServerSocket(port);
handler=new Handler();
handler.start();
timeout=new Timeout();
timeout.start();
if (run) {
server=new Thread(this);
server.start();
}
}
private void log(String m)
{
if (log) System.out.println(m);
}
private void verb(String m)
{
if (verb) System.out.println(m);
}
synchronized
public void sendRequest(CalcRequest req)
{
requests.put(req);
}
synchronized
public void syncEmpty() throws InterruptedException
{
while (!requests.isEmpty()) wait();
}
synchronized
private void doNotify()
{
notify();
}
@Override
public synchronized void addImage(ImageData d)
{
super.addImage(d);
stat.addImage();
}
@Override
public synchronized ImageData removeImage(MandelFileName n)
{
stat.removeImage();
return super.removeImage(n);
}
///////////////////////////////////////////////////////////
// connection handling
///////////////////////////////////////////////////////////
private boolean abort;
public void run()
{
do {
try {
Socket client=socket.accept();
Connection conn=new Connection(client);
conn.start();
}
catch (IOException ex) {
System.out.println("accept faild: "+ex);
}
}
while (!abort);
}
private class Connection extends Thread {
private boolean abort;
private Socket socket;
private ClientData client;
private DataInputStream is;
private DataOutputStream os;
private int vers;
public Connection(Socket s) throws IOException
{
this.socket=s;
is=new DataInputStream(socket.getInputStream());
os=new DataOutputStream(socket.getOutputStream());
this.client=stat.addConnection(this);
verb("connection from "+client.getHost());
}
public Socket getSocket()
{
return socket;
}
public ClientData getClientData()
{
return client;
}
@Override
public void run()
{
try {
try {
String prot=is.readUTF();
if (!PROTOCOL.equals(prot)) {
os.writeUTF("wrong protocol");
close();
return;
}
vers=is.readInt();
verb("accept "+prot+": "+vers);
os.writeUTF(OK);
os.writeInt(vers);
}
catch (IOException ex) {
System.out.println("protocol failed: "+ex);
stat.addError(client);
close();
return;
}
// execution loop
do {
try {
int cmd=is.readInt();
log("read cmd "+cmd);
switch (cmd) {
case REQ_STAT:
handleStat();
break;
case REQ_GET:
handleGet();
break;
case REQ_ANS:
handleAnswer();
break;
default:
stat.addError(client);
os.writeUTF("illegal command.");
break;
}
}
catch (IOException ex) {
abort=true;
System.out.println("connection closed: "+ex);
}
}
while (!abort);
close();
}
finally {
stat.removeConnection(this);
}
}
private void handleStat()
{
try {
int mode=is.readInt();
ServerInfo info=new ServerInfo(stat.getServerData());
info.setWeight(stat.getWeight());
info.setTimeout(stat.getTimeout());
if ((mode&MODE_CLIENTS)!=0) {
for (ClientData c:stat.getClients()) {
info.addClientData(c);
}
}
if ((mode&MODE_IMAGES)!=0) {
for (ImageData c:getActiveImages()) {
info.addImageData(c);
}
}
info.write(os);
}
catch (IOException io) {
close();
}
}
private void handleGet()
{
CalcRequest req=requests.testAndPull();
if (req==null) {
try {
os.writeUTF(EMPTY);
os.writeInt(stat.getTimeout());
stat.notifyContact(client, true);
}
catch (IOException ex) {
close();
}
}
else {
ActiveRequest a=active.put(req,client);
try {
log("sending "+req.getReqId());
os.writeUTF(FOUND);
req.write(os,false);
stat.addRequest(client);
if (requests.isEmpty()) doNotify();
}
catch (IOException ex) {
active.remove(req);
requests.putTop(req);
close();
}
}
}
private void handleAnswer()
{ CalcRequest req=new CalcRequest();
try {
req.read(is,false);
stat.requestDone(client);
log("got answer "+req.getReqId());
}
catch (IOException ex) {
stat.addError(client);
try {
os.writeUTF(RESET);
}
catch (IOException ex1) {
}
close();
return;
}
ActiveRequest a=active.get(req.getReqId());
if (a!=null) {
log("done "+req.getReqId());
a.receive(req);
active.remove(a);
done.put(a.getRequest());
}
try {
os.writeUTF(OK);
}
catch (IOException ex) {
stat.addError(client);
close();
}
}
private void close()
{
try {
is.close();
}
catch (IOException ex) {
}
try {
os.close();
}
catch (IOException ex) {
}
abort=true;
}
}
private class Timeout extends Thread {
private boolean abort=false;
public void abort()
{
abort=true;
}
@Override
public void run()
{ boolean look;
do {
try {
sleep(TIMEOUTSLEEP);
long cur=System.currentTimeMillis();
verb("checking timeouts "+cur);
look=true;
while (look) {
ActiveRequest a=active.peek();
// if (a!=null) verb(" found "+a.getReqId()+": "+a.getTimeout());
if (a!=null && a.getTimeout()<cur) {
active.remove(a);
a.getClient().addTimeout();
verb("repeat "+a.getReqId());
requests.putTop(a.getRequest());
}
else look=false;
}
}
catch (InterruptedException ex) {
}
}
while (!abort);
}
}
private class Handler extends Thread {
private boolean abort=false;
public void abort()
{
abort=true;
}
@Override
public void run()
{
do {
try {
CalcRequest req=done.pull();
req.fireChangeEvent();
}
catch (InterruptedException ex) {
}
}
while (!abort);
}
}
private static class ActiveRequest {
private CalcRequest request;
private ClientData client;
private long timeout;
public ActiveRequest(CalcRequest request, ClientData client)
{
this.request=request;
this.client=client;
timeout=System.currentTimeMillis()+TIMEOUT;
}
public CalcRequest getRequest()
{
return request;
}
public ClientData getClient()
{
return client;
}
public long getReqId()
{
return request.getReqId();
}
public long getTimeout()
{
return timeout;
}
synchronized
public void receive(CalcRequest req)
{
request.setData(req.getData());
request.setNumIt(req.getNumIt());
request.setCCnt(req.getCCnt());
request.setMCnt(req.getMCnt());
request.setMTime(req.getMTime());
request.setMaxIt(req.getMaxIt());
request.setMinIt(req.getMinIt());
}
}
static private class ActiveList {
private HashMap<Long,ActiveRequest> active;
private List<ActiveRequest> sequence;
public ActiveList()
{
active=new HashMap<Long,ActiveRequest>();
sequence=new ArrayList<ActiveRequest>();
}
synchronized
public ActiveRequest put(CalcRequest req, ClientData client)
{ ActiveRequest a=active.get(req.getReqId());
if (a==null) {
a=new ActiveRequest(req,client);
active.put(req.getReqId(),a);
sequence.add(a);
}
return a;
}
synchronized
public ActiveRequest get(long reqid)
{
ActiveRequest a=active.get(reqid);
if (a!=null) sequence.remove(a);
return a;
}
synchronized
public ActiveRequest peek()
{
if (sequence.isEmpty()) return null;
return sequence.get(0);
}
synchronized
public void remove(CalcRequest req)
{
remove(req.getReqId());
}
synchronized
public void remove(ActiveRequest req)
{
remove(req.getReqId());
}
synchronized
public void remove(long reqid)
{
ActiveRequest req=active.get(reqid);
if (req!=null) {
active.remove(reqid);
sequence.remove(req);
}
}
}
static public void main(String[] args)
{
try {
new Server(false).run();
}
catch (IOException ex) {
System.out.println("cannot start server: "+ex);
}
}
private class StatisticHandler {
private List<Connection> connections;
private Map<InetAddress,ClientData> clients;
private ServerData stat;
private List<Entry> hist;
private Entry last;
private int weight;
private int max;
public StatisticHandler(int max)
{
this.stat=new ServerData();
this.clients=new HashMap<InetAddress,ClientData>();
this.connections=new ArrayList<Connection>();
this.hist=new ArrayList<Entry>();
this.max=max;
}
synchronized
private ClientData getClientData(InetAddress addr)
{
ClientData data=clients.get(addr);
if (data==null) {
data=new ClientData(addr.getCanonicalHostName());
clients.put(addr, data);
}
return data;
}
synchronized
public Collection<ClientData> getClients()
{
return Collections.unmodifiableCollection(clients.values());
}
synchronized
public int getWeight()
{
return weight;
}
synchronized
public int getTimeout()
{
if (last==null) return 1;
long diff=System.currentTimeMillis()-hist.get(0).getTime();
if (diff<1000) return 1;
int t=(int)(weight*1000*60/(diff));
if (t<=0) t=1;
verb("t="+t+" diff="+diff+" weight="+weight);
return t;
}
synchronized
protected void addRequest(ClientData client)
{
stat.addRequest(client);
last.addWeight(-1);
}
synchronized
protected ClientData addConnection(Connection conn)
{
ClientData client=getClientData(conn.getSocket().getInetAddress());
stat.addConnection(client);
connections.add(conn);
new Entry();
return client;
}
protected synchronized void addTimeout(ClientData client)
{
stat.addTimeout(client);
}
protected synchronized void addImage()
{
stat.addImage();
}
protected synchronized void addError(ClientData client)
{
stat.addError(client);
}
protected synchronized void requestDone(ClientData client)
{
stat.requestDone(client);
}
protected synchronized void removeImage()
{
stat.removeImage();
}
protected synchronized void removeConnection(Connection conn)
{
stat.removeConnection(conn.getClientData());
connections.remove(conn);
}
protected synchronized long notifyContact(ClientData client,
boolean req)
{
if (req) last.addWeight(1);
return stat.notifyContact(client);
}
public ServerData getServerData()
{
return stat;
}
//////////////////
// history entries
private class Entry {
private long time;
private int weight;
public Entry()
{
time=System.currentTimeMillis();
last=this;
hist.add(this);
addWeight(1);
if (hist.size()>max) {
hist.get(0).remove();
}
}
public void addWeight(int w)
{
weight+=w;
StatisticHandler.this.weight+=w;
}
public void remove()
{
if (hist.contains(this)) {
addWeight(-weight);
hist.remove(this);
}
}
public long getTime()
{
return time;
}
public int getWeight()
{
return weight;
}
}
}
}