/** * 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; }
/** * 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; } } } } }