package network;
import java.io.DataInputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import settings.Settings;
import network.message.Message;
import network.message.MessageFactory;
public class Peer extends Thread{
private InetAddress address;
private ConnectionCallback callback;
private Socket socket;
private OutputStream out;
private Pinger pinger;
private Map<Integer, BlockingQueue<Message>> messages;
public Peer(InetAddress address)
{
this.address = address;
this.messages = Collections.synchronizedMap(new HashMap<Integer, BlockingQueue<Message>>());
}
public Peer(ConnectionCallback callback, Socket socket)
{
try
{
this.callback = callback;
this.socket = socket;
this.address = socket.getInetAddress();
this.messages = Collections.synchronizedMap(new HashMap<Integer, BlockingQueue<Message>>());
//ENABLE KEEPALIVE
//this.socket.setKeepAlive(true);
//TIMEOUT
this.socket.setSoTimeout(1000*60*60);
//CREATE STRINGWRITER
this.out = socket.getOutputStream();
//START COMMUNICATON THREAD
this.start();
//START PINGER
this.pinger = new Pinger(this);
//ON SOCKET CONNECT
this.callback.onConnect(this);
}
catch(Exception e)
{
//FAILED TO CONNECT NO NEED TO BLACKLIST
Logger.getGlobal().info("Failed to connect to : " + address);
}
}
public InetAddress getAddress()
{
return address;
}
public long getPing()
{
return this.pinger.getPing();
}
public void connect(ConnectionCallback callback)
{
this.callback = callback;
try
{
//OPEN SOCKET
this.socket = new Socket(address, Network.PORT);
//ENABLE KEEPALIVE
//this.socket.setKeepAlive(true);
//TIMEOUT
this.socket.setSoTimeout(1000*60*60);
//CREATE STRINGWRITER
this.out = socket.getOutputStream();
//START COMMUNICATON THREAD
this.start();
//START PINGER
this.pinger = new Pinger(this);
//ON SOCKET CONNECT
this.callback.onConnect(this);
}
catch(Exception e)
{
//FAILED TO CONNECT NO NEED TO BLACKLIST
Logger.getGlobal().info("Failed to connect to : " + address);
}
}
public void run()
{
try
{
DataInputStream in = new DataInputStream(socket.getInputStream());
while(true)
{
//READ FIRST 4 BYTES
byte[] messageMagic = new byte[Message.MAGIC_LENGTH];
in.readFully(messageMagic);
if(Arrays.equals(messageMagic, Message.MAGIC))
{
//PROCESS NEW MESSAGE
Message message = MessageFactory.getInstance().parse(this, in);
//Logger.getGlobal().info("received message " + message.getType() + " from " + this.address.toString());
//CHECK IF WE ARE WAITING FOR A MESSAGE WITH THAT ID
if(message.hasId() && this.messages.containsKey(message.getId()))
{
//ADD TO OUR OWN LIST
this.messages.get(message.getId()).add(message);
}
else
{
//CALLBACK
this.callback.onMessage(message);
}
}
else
{
Logger.getGlobal().warning("received message with wrong magic");
//ERROR
callback.onError(this);
return;
}
}
}
catch (Exception e)
{
//e.printStackTrace();
//DISCONNECT
callback.onDisconnect(this);
return;
}
}
public boolean sendMessage(Message message)
{
try
{
//CHECK IF SOCKET IS STILL ALIVE
if(!this.socket.isConnected())
{
//ERROR
callback.onError(this);
return false;
}
//SEND MESSAGE
synchronized(this.out)
{
this.out.write(message.toBytes());
this.out.flush();
}
//RETURN
return true;
}
catch (Exception e)
{
//ERROR
callback.onError(this);
//RETURN
return false;
}
}
public Message getResponse(Message message)
{
//GENERATE ID
int id = (int) ((Math.random() * 1000000) + 1);
//SET ID
message.setId(id);
//PUT QUEUE INTO MAP SO WE KNOW WE ARE WAITING FOR A RESPONSE
BlockingQueue<Message> blockingQueue = new ArrayBlockingQueue<Message>(1);
this.messages.put(id, blockingQueue);
//WHEN FAILED TO SEND MESSAGE
if(!this.sendMessage(message))
{
return null;
}
try
{
Message response = blockingQueue.poll(Settings.getInstance().getConnectionTimeout(), TimeUnit.MILLISECONDS);
this.messages.remove(id);
return response;
}
catch (InterruptedException e)
{
//NO MESSAGE RECEIVED WITHIN TIME;
return null;
}
}
public void onPingFail()
{
//DISCONNECTED
this.callback.onDisconnect(this);
}
public void close()
{
try
{
//STOP PINGER
if(this.pinger != null)
{
this.pinger.stopPing();
}
//CHECK IS SOCKET EXISTS
if(socket != null)
{
//CHECK IF SOCKET IS CONNECTED
if(socket.isConnected())
{
//CLOSE SOCKET
socket.close();
}
}
}
catch(Exception e)
{
}
}
}