package lsr.paxos.network;
import static lsr.common.ProcessDescriptor.processDescriptor;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.BitSet;
import lsr.common.Configuration;
import lsr.common.KillOnExceptionHandler;
import lsr.common.PID;
import lsr.paxos.messages.Message;
import lsr.paxos.messages.MessageFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents network based on UDP. Provides basic methods, sending messages to
* other replicas and receiving messages. This class didn't provide any
* guarantee that sent message will be received by target. It is possible that
* some messages will be lost.
*
*/
public class UdpNetwork extends Network {
private final DatagramSocket datagramSocket;
private final Thread readThread;
private final SocketAddress[] addresses;
private boolean started = false;
/**
* @throws SocketException
*/
public UdpNetwork() throws SocketException {
addresses = new SocketAddress[processDescriptor.numReplicas];
for (int i = 0; i < addresses.length; i++) {
PID pid = processDescriptor.config.getProcess(i);
addresses[i] = new InetSocketAddress(pid.getHostname(), pid.getReplicaPort());
}
int localPort = processDescriptor.getLocalProcess().getReplicaPort();
logger.info("Opening port: {}", localPort);
datagramSocket = new DatagramSocket(localPort);
datagramSocket.setReceiveBufferSize(Configuration.UDP_RECEIVE_BUFFER_SIZE);
datagramSocket.setSendBufferSize(Configuration.UDP_SEND_BUFFER_SIZE);
readThread = new Thread(new SocketReader(), "UdpReader");
readThread.setUncaughtExceptionHandler(new KillOnExceptionHandler());
readThread.setDaemon(true);
}
@Override
public void start() {
if (!started) {
readThread.start();
started = true;
} else {
throw new RuntimeException("Starting UDP network multiple times!");
}
}
/**
* Reads messages from the network and enqueues them to be handled by the
* dispatcher thread.
*/
private class SocketReader implements Runnable {
public void run() {
logger.info("{} thread started. Waiting for UDP messages",
Thread.currentThread().getName());
try {
while (!Thread.interrupted()) {
byte[] buffer = new byte[processDescriptor.maxUdpPacketSize + 4];
// Read message and enqueue it for processing.
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
datagramSocket.receive(dp);
ByteArrayInputStream bais = new ByteArrayInputStream(dp.getData(),
dp.getOffset(), dp.getLength());
DataInputStream dis = new DataInputStream(bais);
int sender = dis.readInt();
byte[] data = new byte[dp.getLength() - 4];
dis.read(data);
Message message = MessageFactory.readByteArray(data);
logger.debug("Received from {}:{}", sender, message);
fireReceiveMessage(message, sender);
}
} catch (IOException e) {
throw new RuntimeException("Fatal error.", e);
}
throw new RuntimeException("UDP network interrupted.");
}
}
/**
* Blocks until there is space in the OS to buffer the message. Normally it
* should return immediately. Specified byte array should be serialized
* message (without any header like id of replica).
* <p>
* The sentMessage event in listeners is not fired after calling this
* method.
*
* @param message - the message to send
* @param destinations - the id's of replicas to send message to
* @throws IOException if an I/O error occurs
*/
protected void send(byte[] message, BitSet destinations) {
// prepare packet to send
byte[] data = new byte[message.length + 4];
ByteBuffer.wrap(data).putInt(processDescriptor.localId).put(message);
DatagramPacket dp = new DatagramPacket(data, data.length);
for (int i = destinations.nextSetBit(0); i >= 0; i = destinations.nextSetBit(i + 1)) {
dp.setSocketAddress(addresses[i]);
try {
datagramSocket.send(dp);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void send(Message message, BitSet destinations) {
message.setSentTime();
byte[] messageBytes = message.toByteArray();
if (messageBytes.length > processDescriptor.maxUdpPacketSize + 4) {
throw new RuntimeException("Data packet too big. Size: " +
messageBytes.length + ", limit: " +
processDescriptor.maxUdpPacketSize +
". Packet not sent.");
}
send(messageBytes, destinations);
}
@Override
protected void send(Message message, int destination) {
// prepare packet to send
byte[] data = new byte[message.byteSize() + 4];
ByteBuffer bb = ByteBuffer.wrap(data);
bb.putInt(processDescriptor.localId);
message.writeTo(bb);
DatagramPacket dp = new DatagramPacket(data, data.length);
dp.setSocketAddress(addresses[destination]);
try {
datagramSocket.send(dp);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private final static Logger logger = LoggerFactory.getLogger(UdpNetwork.class);
}