package lsr.paxos.network; import static lsr.common.ProcessDescriptor.processDescriptor; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import lsr.common.KillOnExceptionHandler; import lsr.paxos.messages.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TcpNetwork extends Network implements Runnable { private final TcpConnection[][] activeConnections; private final List<TcpConnection> allConnections = new ArrayList<TcpConnection>(); private final ServerSocket server; private final Thread acceptorThread; private boolean started = false; /** * Creates new network for handling connections with other replicas. * * @throws IOException if opening server socket fails */ public TcpNetwork() throws IOException { activeConnections = new TcpConnection[processDescriptor.numReplicas][2]; logger.info("Opening port {}", processDescriptor.getLocalProcess().getReplicaPort()); server = new ServerSocket(); server.setReceiveBufferSize(256 * 1024); server.bind(new InetSocketAddress((InetAddress) null, processDescriptor.getLocalProcess().getReplicaPort())); acceptorThread = new Thread(this, "TcpNetwork"); acceptorThread.setUncaughtExceptionHandler(new KillOnExceptionHandler()); } @Override public void start() { if (!started) { for (int i = 0; i < activeConnections.length; i++) { activeConnections[i][0] = null; activeConnections[i][1] = null; if (i == processDescriptor.localId) continue; TcpConnection tcpConn = new TcpConnection(this, processDescriptor.config.getProcess(i), i, true); allConnections.add(tcpConn); tcpConn.start(); } acceptorThread.start(); started = true; } else { throw new RuntimeException("Starting TCP network multiple times!"); } } /** * Sends binary data to specified destination. * * @param message - binary data to send * @param destination - id of replica to send data to * @return true if message was sent; false if some error occurred */ protected void send(byte[] message, int destination) { if (activeConnections[destination][0] != null) activeConnections[destination][0].send(message); } protected void send(Message message, int destination) { send(message.toByteArray(), destination); } @Override public void send(Message message, BitSet destinations) { assert !destinations.isEmpty() : "Sending a message to no one"; byte[] bytes = message.toByteArray(); for (int i = destinations.nextSetBit(0); i >= 0; i = destinations.nextSetBit(i + 1)) { send(bytes, i); } } /** * Main loop which accepts incoming connections. */ public void run() { logger.info("{} thread started", Thread.currentThread().getName()); while (true) { try { Socket socket = server.accept(); initializeConnection(socket); } catch (IOException e) { throw new RuntimeException(e); } } } private void initializeConnection(Socket socket) { try { logger.info("Received connection from {}", socket.getRemoteSocketAddress()); socket.setReceiveBufferSize(TcpConnection.TCP_BUFFER_SIZE); socket.setSendBufferSize(TcpConnection.TCP_BUFFER_SIZE); socket.setTcpNoDelay(true); if (logger.isDebugEnabled()) logger.debug("Passive. RcvdBuffer: {}, SendBuffer: {}", socket.getReceiveBufferSize(), socket.getSendBufferSize()); DataInputStream input = new DataInputStream( new BufferedInputStream(socket.getInputStream())); DataOutputStream output = new DataOutputStream( new BufferedOutputStream(socket.getOutputStream())); int replicaId = input.readInt(); if (replicaId < 0 || replicaId >= processDescriptor.numReplicas) { logger.error("Remoce host id is out of range: {}", replicaId); socket.close(); return; } if (replicaId == processDescriptor.localId) { logger.error("Remote replica has same id as local: {}", replicaId); socket.close(); return; } TcpConnection tcpConn = new TcpConnection(this, processDescriptor.config.getProcess(replicaId), replicaId, false); tcpConn.setConnection(socket, input, output); addConnection(replicaId, tcpConn); tcpConn.start(); } catch (IOException e) { logger.error("Initialization of accepted connection failed.", e); try { socket.close(); } catch (IOException e1) { } } } /* package private */void addConnection(int replicaId, TcpConnection tcpConn) { synchronized (activeConnections) { if (activeConnections[replicaId][0] == null) { activeConnections[replicaId][0] = tcpConn; logger.info(processDescriptor.logMark_Benchmark, "Tcp connected {}", replicaId); } else { if (activeConnections[replicaId][1] == null) { if (activeConnections[replicaId][0].isActive() ^ tcpConn.isActive()) { activeConnections[replicaId][1] = tcpConn; } else { activeConnections[replicaId][0].stopAsync(); activeConnections[replicaId][0] = tcpConn; } } else { if (activeConnections[replicaId][1].isActive() ^ tcpConn.isActive()) { activeConnections[replicaId][0].stopAsync(); activeConnections[replicaId][0] = tcpConn; } else { activeConnections[replicaId][1].stopAsync(); activeConnections[replicaId][1] = tcpConn; } } } } } /* package private */void removeConnection(int replicaId, TcpConnection tcpConn) { synchronized (activeConnections) { if (activeConnections[replicaId][1] == tcpConn) { activeConnections[replicaId][1] = null; } else if (activeConnections[replicaId][0] == tcpConn) { activeConnections[replicaId][0] = activeConnections[replicaId][1]; activeConnections[replicaId][1] = null; } if (!tcpConn.isActive()) allConnections.remove(tcpConn); } } public void closeAll() { for (TcpConnection c : allConnections) { try { if (c != null) c.stop(); } catch (InterruptedException e) { e.printStackTrace(); } } } private final static Logger logger = LoggerFactory.getLogger(TcpNetwork.class); }