/** * Send a data packet and wait for ack in one operation. This method employs a timer that resends * the packet until an ack is received (or the timeout is reached). <br> * <br> * This method sets the {@link #lastDataPacketSent} variable. This method can only be used in the * Established state, see {@link State}. * * @param packet the packet to be sent. * @return The ack-package received for the send packet (NB: ack can be null) * @throws IOException thrown if unable to send packet. * @see no.ntnu.fp.net.cl.ClSocket#send(KtnDatagram) */ protected synchronized KtnDatagram sendDataPacketWithRetransmit(KtnDatagram packet) throws IOException { if (state != State.ESTABLISHED) throw new IllegalStateException("Should only be used in ESTABLISHED state."); if (packet.getFlag() != Flag.NONE) throw new IllegalArgumentException("Packet must be a data packet."); /* * Algorithm: 1 Start a timer used to resend the packet with a specified * interval, and that immediately starts trying (sending the first * packet as well as the retransmits). 2 Wait for the ACK using * receiveAck(). 3 Cancel the timer. 4 Return the ACK-packet. */ lastDataPacketSent = packet; // Create a timer that sends the packet and retransmits every // RETRANSMIT milliseconds until cancelled. Timer timer = new Timer(); timer.scheduleAtFixedRate(new SendTimer(new ClSocket(), packet), 0, RETRANSMIT); KtnDatagram ack = receiveAck(); timer.cancel(); return ack; }
/** * Send an ack or synack for the given packet. <br> * If the send fails, there is no retransmission of the ack-packet: Just wait for the other side * to retransmit the original packet.<br> * <br> * This method relies on {@link #constructInternalPacket(Flag)}, i.e. myAddress, myPort, * remoteAddress, remotePort and sequenceNo must be initialized properly. * * @param packetToAck The packet that should be acked * @param synAck true if a synack should be sent, false if a regular ack, see {@link * KtnDatagram.Flag}. * @throws ConnectException Thrown if unable to send packet. * @see #constructInternalPacket(Flag) */ protected synchronized void sendAck(KtnDatagram packetToAck, boolean synAck) throws IOException, ConnectException { /* * Algorithm: Generate a new ack packet based on the packet given as * input Try to send the ack Catch a ConnectException if the sending * failed - and write this to the Log. */ int tries = 3; boolean sent = false; KtnDatagram ackToSend = constructInternalPacket(synAck ? Flag.SYN_ACK : Flag.ACK); ackToSend.setAck(packetToAck.getSeq_nr()); // Send the ack, trying at most `tries' times. Log.writeToLog(ackToSend, "Sending Ack: " + ackToSend.getAck(), "AbstractConnection"); do { try { new ClSocket().send(ackToSend); sent = true; } catch (ClException e) { Log.writeToLog( ackToSend, "CLException: Could not establish a " + "connection to the specified address/port!", "AbstractConnection"); } catch (ConnectException e) { // Silently ignore: Maybe recipient was processing and didn't // manage to call receiveAck() before we were ready to send. try { Thread.sleep(100); } catch (InterruptedException ex) { } } } while (!sent && (tries-- > 0)); if (!sent) { nextSequenceNo--; throw new ConnectException("Unable to send ACK."); } }
/** * Construct a datagram with the given flag. <br> * <br> * Note: This method *depends* on the values of `remotePort', `remoteAddress', `myPort', * `myAddress' and `sequenceNo'. Failing to set these before calling this method causes undefined * behaviour. Also note that if you want to set values to something else than the default, you * must construct the packet manually or alter the returned object.<br> * <br> * This method also increments the sequenceNo.<br> * <br> * This method sets the following fields: * * <ol> * <li>Remote address * <li>Remote port * <li>Local address * <li>Local port * <li>Flag * <li>Sequence no. * <li>Payload to null. * </ol> * * @param flag Flag for the packet, see {@link KtnDatagram.Flag}. Setting this to * KtnDatagram.Flag.NONE constructs a packet with no flag or data, and makes no sense. * @return Initialised flagged datagram. */ protected KtnDatagram constructInternalPacket(Flag flag) { KtnDatagram packet = new KtnDatagram(); packet.setDest_port(remotePort); packet.setDest_addr(remoteAddress); packet.setSrc_addr(myAddress); packet.setSrc_port(myPort); packet.setFlag(flag); packet.setSeq_nr(nextSequenceNo++); packet.setPayload(null); return packet; }
/** * Construct a datagram with the given payload. <br> * <br> * Note: This method *depends* on the values of `remotePort', `remoteAddress', `myPort', * `myAddress' and `sequenceNo'. Failing to set these before calling this method causes undefined * behaviour. Also note that if you want to set values to something else than the default, you * must construct the packet manually or alter the returned object.<br> * <br> * This method also increments the sequenceNo.<br> * <br> * This method sets the following fields: * * <ol> * <li>Remote address * <li>Remote port * <li>Local address * <li>Local port * <li>Flag to NONE. * <li>Sequence no. * <li>Payload. * </ol> * * @param payload Payload for packet, can not be null. * @return Initialised datagram. */ protected KtnDatagram constructDataPacket(String payload) { if (payload == null) throw new IllegalArgumentException("Payload can not be null."); KtnDatagram packet = new KtnDatagram(); packet.setDest_port(remotePort); packet.setDest_addr(remoteAddress); packet.setSrc_addr(myAddress); packet.setSrc_port(myPort); packet.setFlag(Flag.NONE); packet.setSeq_nr(nextSequenceNo++); packet.setPayload(payload); return packet; }
/** * Waits for an ACK or SYN_ACK. Blocks until the ack is recieved. Returns null if no ack recieved * after the specified time, see {@link AbstractConnection#TIMEOUT}. <br> * <br> * If a FIN-packet is received and the state is not ESTABLISHED, this will also be returned.<br> * <br> * If a FIN-packet is received and the connection is in ESTABLISHED state, an EOFException is * thrown. It calls {@link #isValid(KtnDatagram)} on FIN-packets in ESTABLISHED state, before an * EOFException is thrown. * * @return The ACK or SYN_ACK KtnDatagram recieved (can be null), may also be a FIN if not in * established state. * @throws IOException If caused by the underlying connectionless layer. * @throws EOFException If a FIN-packet is received in ESTABLISHED state. */ protected KtnDatagram receiveAck() throws IOException, EOFException { /* * Acquire monitor for this instance, and see if another thread runs * receive on our port. If so, see if that thread gets the packet that * was meant for us. */ synchronized (this) { long before, after; before = System.currentTimeMillis(); while (isReceiving) { try { wait(TIMEOUT); // wait with timeout } catch (InterruptedException e) { /* do nothing */ } after = System.currentTimeMillis(); // If an ack for us has arrived, return it. // check internalQueue if (!internalQueue.isEmpty()) { KtnDatagram packet = internalQueue.get(0); if (packet.getFlag() == Flag.ACK || packet.getFlag() == Flag.SYN_ACK) { return internalQueue.remove(0); } } // If no packet arrived, see if timeout has expired. else if ((after - before) > TIMEOUT) { return null; } } // When we get here, this thread has not got its packet, and it's // allowed to enter the listening part of doReceive(). isReceiving = true; } Log.writeToLog("Waiting for incoming packet in receiveAck()", "AbstractConnection"); KtnDatagram incomingPacket; // We are waiting for an ack or syn_ack packet long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIMEOUT) { ClSocketReceiver receiver = new ClSocketReceiver(myPort); receiver.start(); // Wait at most what is left of the TIMEOUT period for thread to // die, but never less than 1 millisecond try { receiver.join(Math.max(TIMEOUT - (System.currentTimeMillis() - start), 1)); } catch (InterruptedException e) { /* do nothing */ } receiver.stopReceive(); incomingPacket = receiver.getPacket(); if (incomingPacket == null) { // No packet was received synchronized (this) { isReceiving = false; notifyAll(); return null; } } else { // We have a packet if (incomingPacket.getFlag() != Flag.NONE) { // Packet is internal Log.writeToLog("Received an internal packet in receiveAck", "AbstractConnection"); if (incomingPacket.getFlag() == Flag.FIN && state == State.ESTABLISHED) { // A FIN-packet has arrived in established state, // stop receiving and throw and exception disconnectRequest = incomingPacket; synchronized (this) { isReceiving = false; notifyAll(); throw new EOFException("FIN packet received."); } } else if (incomingPacket.getFlag() == Flag.ACK || incomingPacket.getFlag() == Flag.SYN_ACK || incomingPacket.getFlag() == Flag.FIN) { // Not a FIN packet in established state, return if it // is SYN, SYN_ACK or FIN synchronized (this) { isReceiving = false; notifyAll(); return incomingPacket; } } else { // Not a packet we want to return, continue looping. synchronized (this) { internalQueue.add(incomingPacket); notifyAll(); } } } else { // Packet was meant for the application, continue // listening until timeout. Log.writeToLog("Received an external packet in receiveAck", "AbstractConnection"); synchronized (this) { synchronized (this) { externalQueue.add(incomingPacket); notifyAll(); } } } } } // We have now waited at least TIMEOUT milliseconds, still no // packet. synchronized (this) { isReceiving = false; notifyAll(); return null; } }
/** * Receives a packet from the connectionless layer. This function handles concurrency issues * related to that only one thread may listen to a port at the same time.<br> * <br> * It calls {@link #isValid(KtnDatagram)} on FIN-packets in ESTABLISHED state, before an * EOFException is thrown. * * @param internal true if you want to receive non-external packet, i.e. not a packet with data * intended for the application. False otherwise. * @return A received datagram * @throws IOException If the underlying connectionless layer throws an IOException. * @throws EOFException If a packet with a FIN-flag was received in ESTABLISHED state. */ protected KtnDatagram receivePacket(boolean internal) throws IOException, EOFException { /* * Acquire monitor for this instance, and see if another thread runs * receive on our port. If so, see if that thread gets the packet that * was meant for us. */ synchronized (this) { long before, after; before = System.currentTimeMillis(); while (isReceiving) { try { if (internal) wait(TIMEOUT); // wait with timeout else wait(); // wait (potentially) forever } catch (InterruptedException e) { /* do nothing */ } after = System.currentTimeMillis(); // If a packet for us has arrived, return it. if (internal) { // Case 1: Internal (protocol) caller, check internalQueue if (!internalQueue.isEmpty()) { return internalQueue.remove(0); } // If no packet arrived, see if timeout has expired. else if ((after - before) > TIMEOUT) { return null; } } else { // Case 2: Non-internal (application) caller, check // externalQueue if (!externalQueue.isEmpty()) { return externalQueue.remove(0); } // else try loop again unless connection is free } } // When we get here, this thread has not got its packet, and it's // allowed to enter the listening part of doReceive(). isReceiving = true; } Log.writeToLog("Waiting for incoming packet in doReceive()", "AbstractConnection"); KtnDatagram incomingPacket; /* * Waiting for internal and external packets should be handled * differently. Waiting for internal packets should time out. */ if (internal) { // We are waiting for an internal packet, ie. a packet with a flag long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIMEOUT) { ClSocketReceiver receiver = new ClSocketReceiver(myPort); receiver.start(); // Wait at most what is left of the TIMEOUT period for thread to // die, but never less than 1 millisecond try { long timeout = TIMEOUT - (System.currentTimeMillis() - start); receiver.join(Math.max(timeout, 1)); } catch (InterruptedException e) { /* do nothing */ } receiver.stopReceive(); incomingPacket = receiver.getPacket(); if (incomingPacket == null) { // No packet was received synchronized (this) { isReceiving = false; notifyAll(); return null; } } else { // We have a packet if (incomingPacket.getFlag() != Flag.NONE) { // Packet is internal Log.writeToLog("Received an internal packet in doReceive", "AbstractConnection"); if (incomingPacket.getFlag() == Flag.FIN && state == State.ESTABLISHED) { // A FIN-packet has arrived in established state, // stop receiving and throw and exception disconnectRequest = incomingPacket; synchronized (this) { isReceiving = false; notifyAll(); throw new EOFException("FIN packet received."); } } else { // Not a FIN packet in established state, return it // normally. synchronized (this) { isReceiving = false; notifyAll(); return incomingPacket; } } } else { // Packet was meant for the application, continue // listening until timeout. Log.writeToLog("Received an external packet in doReceive", "AbstractConnection"); synchronized (this) { synchronized (this) { externalQueue.add(incomingPacket); notifyAll(); } } } } } // We have now waited at least TIMEOUT milliseconds, still no // packet. synchronized (this) { isReceiving = false; notifyAll(); return null; } } else { // We are waiting for a packet to an external application, ie. a // packet with no flags. Can possibly wait forever. incomingPacket = new ClSocket().receive(myPort); if (incomingPacket == null) { // We should get a packet, try again. synchronized (this) { isReceiving = false; notifyAll(); } return receivePacket(internal); } else { // We have a packet if (incomingPacket.getFlag() != Flag.NONE) { // Packet is internal Log.writeToLog("Received an internal packet in doReceive", "AbstractConnection"); if (incomingPacket.getFlag() == Flag.FIN && state == State.ESTABLISHED) { // A FIN-packet has arrived in established state, // stop receiving and throw and exception disconnectRequest = incomingPacket; synchronized (this) { isReceiving = false; notifyAll(); throw new EOFException("FIN packet received."); } } else { // Not a FIN packet, continue listening synchronized (this) { internalQueue.add(incomingPacket); isReceiving = false; notifyAll(); } return receivePacket(internal); } } else { // Packet was meant for the application, yei! Log.writeToLog("Received an external packet in doReceive", "AbstractConnection"); synchronized (this) { isReceiving = false; notifyAll(); return incomingPacket; } } } } }