From acc8707c3f052e7cca9b4deba3b59f4db2893ae4 Mon Sep 17 00:00:00 2001 From: "Claudio Maggioni (maggicl)" Date: Wed, 6 May 2020 19:39:56 +0200 Subject: [PATCH] hw2: done Sender --- hw2/.idea/.gitignore | 3 + hw2/.idea/dictionaries/maggicl.xml | 7 + hw2/.idea/libraries/transport.xml | 9 ++ hw2/.idea/misc.xml | 6 + hw2/.idea/modules.xml | 8 + hw2/.idea/uiDesigner.xml | 124 ++++++++++++++++ hw2/.idea/vcs.xml | 6 + hw2/hw2.iml | 12 ++ hw2/src/GBNTReceiver.java | 14 ++ hw2/src/GBNTSender.java | 166 +++++++++++++++++++++ hw2/src/SAWReceiver2.java | 89 ++++++++++++ hw2/src/SAWSender2.java | 226 +++++++++++++++++++++++++++++ hw2/transport.jar | Bin 0 -> 8903 bytes 13 files changed, 670 insertions(+) create mode 100644 hw2/.idea/.gitignore create mode 100644 hw2/.idea/dictionaries/maggicl.xml create mode 100644 hw2/.idea/libraries/transport.xml create mode 100644 hw2/.idea/misc.xml create mode 100644 hw2/.idea/modules.xml create mode 100644 hw2/.idea/uiDesigner.xml create mode 100644 hw2/.idea/vcs.xml create mode 100644 hw2/hw2.iml create mode 100644 hw2/src/GBNTReceiver.java create mode 100644 hw2/src/GBNTSender.java create mode 100644 hw2/src/SAWReceiver2.java create mode 100644 hw2/src/SAWSender2.java create mode 100644 hw2/transport.jar diff --git a/hw2/.idea/.gitignore b/hw2/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/hw2/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/hw2/.idea/dictionaries/maggicl.xml b/hw2/.idea/dictionaries/maggicl.xml new file mode 100644 index 0000000..575e258 --- /dev/null +++ b/hw2/.idea/dictionaries/maggicl.xml @@ -0,0 +1,7 @@ + + + + ewma + + + \ No newline at end of file diff --git a/hw2/.idea/libraries/transport.xml b/hw2/.idea/libraries/transport.xml new file mode 100644 index 0000000..16596b2 --- /dev/null +++ b/hw2/.idea/libraries/transport.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/hw2/.idea/misc.xml b/hw2/.idea/misc.xml new file mode 100644 index 0000000..37e641e --- /dev/null +++ b/hw2/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/hw2/.idea/modules.xml b/hw2/.idea/modules.xml new file mode 100644 index 0000000..87db25b --- /dev/null +++ b/hw2/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/hw2/.idea/uiDesigner.xml b/hw2/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/hw2/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hw2/.idea/vcs.xml b/hw2/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/hw2/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/hw2/hw2.iml b/hw2/hw2.iml new file mode 100644 index 0000000..c583ea4 --- /dev/null +++ b/hw2/hw2.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/hw2/src/GBNTReceiver.java b/hw2/src/GBNTReceiver.java new file mode 100644 index 0000000..bf3cf83 --- /dev/null +++ b/hw2/src/GBNTReceiver.java @@ -0,0 +1,14 @@ +import transport.Receiver; +import transport.TimeoutAction; + +public class GBNTReceiver extends Receiver implements TimeoutAction { + @Override + public void timeoutAction() { + + } + + @Override + protected void unreliableReceive(byte[] bytes, int i, int i1) { + + } +} diff --git a/hw2/src/GBNTSender.java b/hw2/src/GBNTSender.java new file mode 100644 index 0000000..06fa398 --- /dev/null +++ b/hw2/src/GBNTSender.java @@ -0,0 +1,166 @@ +import transport.TimeoutAction; + +import java.util.*; + +public class GBNTSender extends transport.Sender implements TimeoutAction { + + private static void writeBigEndianChar(byte[] dest, char data) { + dest[0] = (byte) ((data >> 8) & 0xFF); + dest[1] = (byte) (data & 0xFF); + } + + private static char readBigEndianChar(byte[] src, int offset) { + return (char) (src[offset] << 8 + src[offset + 1]); + } + + private static char incrementAndRollover(char c) { + return (char) ((c + 1) % 65536); + } + + private final Map packetMap = new HashMap<>(); + + private class Packet { + private final byte[] contents; + private final char sequence; + private final int length; + private long estimateStart; + + Packet(char sequence, byte[] buffer, int offset, int length) { + this.sequence = sequence; + this.length = Math.min(length + 2, MAX_PACKET_SIZE); + contents = new byte[MAX_PACKET_SIZE]; + writeBigEndianChar(contents, sequence); + System.arraycopy(buffer, offset, this.contents, 0, this.length); + packetMap.put(this.sequence, this); + } + + void send() { + estimateStart = System.currentTimeMillis(); + unreliableSend(contents, 0, length); + } + + void destroy() { + packetMap.remove(this.sequence); + } + + private long getRTT() { + return System.currentTimeMillis() - estimateStart; + } + + int getLength() { + return length; + } + + char getSequence() { return sequence; } + } + + private static final char W = 10; + private char base = (char) (new Random().nextInt() % 65536); + private char waitingACK = 0; + private int timeoutMs = 1000; + private double rttEWMA = 0; + private double devRttEWMA = 0; + + private State state = State.SETUP; + private final Queue packets = new ArrayDeque<>(); + + enum State { + SETUP, + SENDING, + CLOSING, + } + + public GBNTSender() { + sendSetup(); + } + + private void computeTimeoutLength(long rttMIllis) { + devRttEWMA = 0.75 * devRttEWMA + 0.25 * (rttEWMA - rttMIllis); + rttEWMA = 0.875 * rttEWMA + 0.125 * rttMIllis; + timeoutMs = (int) (rttEWMA + 4 * devRttEWMA); + } + + private void flushAckedPackets() { + for (Packet p = packets.peek(); + p != null && p.getSequence() != base; + p = packets.peek()) { + packets.remove(); + p.destroy(); + } + } + + private byte[] createSetupPacket() { + byte[] packet = new byte[2]; + writeBigEndianChar(packet, base); + return packet; + } + + /** + * Sends the randomly chosen sequence number + */ + private void sendSetup() { + unreliableSend(createSetupPacket(), 0, 2); + blockSender(); + } + + @Override + protected int reliableSend(byte[] bytes, int offset, int length) { + if (state != State.SENDING) throw new IllegalStateException(); + + Packet p = new Packet(base, bytes, offset, length); + waitingACK++; + packets.add(p); + p.send(); + setTimeout(timeoutMs, this); + + if (waitingACK >= W) { + blockSender(); + } + + return p.getLength(); + } + + @Override + public void close() { + state = State.CLOSING; + if (waitingACK > 0) { + blockSender(); + } + } + + @Override + public void timeoutAction() { + for (Packet p : packets) { + p.send(); + } + } + + @Override + protected void unreliableReceive(byte[] bytes, int offset, int length) { + switch (state) { + case SETUP: + byte[] expected = createSetupPacket(); + if (bytes[offset] == expected[0] && bytes[1] == expected[offset + 1]) { + state = State.SENDING; + resumeSender(); + } else { + sendSetup(); + } + break; + case SENDING: + case CLOSING: + char ackSeq = readBigEndianChar(bytes, offset); + + cancelTimeout(this); + computeTimeoutLength(packetMap.get(ackSeq).getRTT()); + + boolean mustResume = waitingACK == W && state == State.SENDING; + waitingACK -= (ackSeq - base); + base = incrementAndRollover(ackSeq); + flushAckedPackets(); + if (mustResume || (waitingACK == 0 && state == State.CLOSING)) { + resumeSender(); + } + } + } +} diff --git a/hw2/src/SAWReceiver2.java b/hw2/src/SAWReceiver2.java new file mode 100644 index 0000000..350bd25 --- /dev/null +++ b/hw2/src/SAWReceiver2.java @@ -0,0 +1,89 @@ +import transport.*; + +import java.net.*; +import java.io.*; + +public class SAWReceiver2 extends Receiver implements TimeoutAction { + private static final int R0 = 0; + private static final int R1 = 1; + private static final int CLOSED = 2; + + private byte[] ack1; + private byte[] ack0; + private byte[] ackfin; + private int state; + + public SAWReceiver2() { + ack0 = new byte[1]; + ack0[0] = 0; + + ack1 = new byte[1]; + ack1[0] = 1; + + ackfin = new byte[1]; + ackfin[0] = 2; + + state = R0; + } + + public void timeoutAction() { + try { + disconnect(); + } catch (java.lang.InterruptedException ex) { + System.out.println("Thread interrupted. Exiting directly."); + System.exit(0); + } + } + + private static void print_packet(byte[] buffer, int offset, int length) { + int end = offset + length; + System.out.print("\n["); + while (offset < end) + System.out.print(buffer[offset++] + " "); + System.out.print("]"); + } + + public void unreliableReceive(byte[] buffer, int offset, int length) { + // print_packet(buffer, offset, length); + cancelTimeout(this); + setTimeout(20000, this); + // try { Thread.sleep(1000); } catch (InterruptedException ignored) { } + switch (state) { + case R0: + System.out.print("R0:"); + if (length > 0 && buffer[offset] == 0) { + System.out.print("->R1 "); + state = R1; + deliver(buffer, offset + 1, length - 1); + unreliableSend(ack0, 0, 1); + } else if (length > 0 && buffer[offset] == 2) { + System.out.print("->CLOSED "); + state = CLOSED; + deliver(buffer, offset + 1, length - 1); + unreliableSend(ackfin, 0, 1); + deliver(null, 0, END_OF_STREAM); + } else { + System.out.print("!->R0 "); + unreliableSend(ack1, 0, 1); + } + return; + case R1: + System.out.print("R1:"); + if (length > 0 && buffer[offset] == 1) { + System.out.print("->R0 "); + state = R0; + deliver(buffer, offset + 1, length - 1); + unreliableSend(ack1, 0, 1); + } else if (length > 0 && buffer[offset] == 2) { + System.out.print("->CLOSED "); + state = CLOSED; + unreliableSend(ackfin, 0, 1); + deliver(null, 0, END_OF_STREAM); + } else { + System.out.print("!->R1 "); + unreliableSend(ack0, 0, 1); + } + return; + } + } +} diff --git a/hw2/src/SAWSender2.java b/hw2/src/SAWSender2.java new file mode 100644 index 0000000..b03f1b4 --- /dev/null +++ b/hw2/src/SAWSender2.java @@ -0,0 +1,226 @@ +import transport.*; + +public class SAWSender2 extends Sender implements TimeoutAction { + + private static final int S0 = 0; + private static final int ACK0 = 1; + private static final int S1 = 2; + private static final int ACK1 = 3; + private static final int ACK0FIN = 4; // waiting for ack0, then go to FIN + private static final int ACK1FIN = 5; // waiting for ack1, then go to FIN + private static final int FIN = 6; // waiting for final ack, then go to CLOSE + private static final int CLOSED = 7; + + private static final int SENDER_TIMEOUT_MS = 5000; + + private int state; + private byte[] data_pkt; + private int data_pkt_len; + + public SAWSender2() { + state = S0; + data_pkt = new byte[MAX_PACKET_SIZE]; + data_pkt_len = 0; + } + + private void make_packet(int seq, byte[] buffer, int offset, int length) { + if (length + 1 > MAX_PACKET_SIZE) { + data_pkt_len = MAX_PACKET_SIZE; + } else { + data_pkt_len = length + 1; + } + data_pkt[0] = (byte) seq; + for (int i = 1; i < data_pkt_len; ++i) + data_pkt[i] = buffer[offset++]; + } + + private static void print_packet(byte[] buffer, int offset, int length) { + int end = offset + length; + System.out.print("\n["); + while (offset < end) + System.out.print(buffer[offset++] + " "); + System.out.print("]"); + } + + public void unreliableReceive(byte[] buffer, int offset, int length) { + // print_packet(buffer, offset, length); + switch (state) { + case S0: + System.err.println("SAWSender.unreliableReceive: received packet in S0. Ignoring."); + return; + + case S1: + System.err.println("SAWSender.unreliableReceive: received packet in S1. Ignoring."); + return; + + case ACK0: + System.out.print("ACK0:"); + if (length > 0 && buffer[offset] == 0) { + System.out.print("->S1 "); + state = S1; + cancelTimeout(this); + allowDisconnect(); + resumeSender(); + } else { + System.out.print("!->ACK0 "); + cancelTimeout(this); + setTimeout(SENDER_TIMEOUT_MS, this); + unreliableSend(data_pkt, 0, data_pkt_len); + } + return; + + case ACK0FIN: + System.out.print("ACK0FIN:"); + if (length > 0 && buffer[offset] == 0) { + cancelTimeout(this); + goToFinState(); + } else { + System.out.print("!->ACK1FIN "); + cancelTimeout(this); + setTimeout(SENDER_TIMEOUT_MS, this); + unreliableSend(data_pkt, 0, data_pkt_len); + } + return; + + case ACK1: + System.out.print("ACK1:"); + if (length > 0 && buffer[offset] == 1) { + System.out.print("->S0 "); + state = S0; + cancelTimeout(this); + allowDisconnect(); + resumeSender(); + } else { + System.out.print("!->ACK1 "); + cancelTimeout(this); + setTimeout(SENDER_TIMEOUT_MS, this); + unreliableSend(data_pkt, 0, data_pkt_len); + } + return; + + case ACK1FIN: + System.out.print("ACK1FIN:"); + if (length > 0 && buffer[offset] == 1) { + cancelTimeout(this); + goToFinState(); + } else { + System.out.print("!->ACK1FIN "); + cancelTimeout(this); + setTimeout(SENDER_TIMEOUT_MS, this); + unreliableSend(data_pkt, 0, data_pkt_len); + } + return; + + case FIN: + System.out.print("FIN:"); + if (length > 0 && buffer[offset] == 2) { + System.out.print("->CLOSED "); + state = CLOSED; + cancelTimeout(this); + allowDisconnect(); + resumeSender(); + } else { + System.out.print("!->FIN "); + cancelTimeout(this); + setTimeout(SENDER_TIMEOUT_MS, this); + unreliableSend(data_pkt, 0, data_pkt_len); + } + return; + } + } + + public void timeoutAction() { + switch (state) { + case S0: + System.err.println("SAWSender.timeoutAction: timeout in S0. Ignoring timeout."); + return; + + case S1: + System.err.println("SAWSender.timeoutAction: timeout in S1. Ignoring timeout."); + return; + + case FIN: + case ACK0: + case ACK1: + case ACK0FIN: + case ACK1FIN: + System.out.print("T! "); + cancelTimeout(this); + setTimeout(SENDER_TIMEOUT_MS, this); + unreliableSend(data_pkt, 0, data_pkt_len); + } + } + + public int reliableSend(byte[] buffer, int offset, int length) { + switch (state) { + case S0: + System.out.print("S0:->ACK0 "); + blockSender(); + blockDisconnect(); + make_packet(0, buffer, offset, length); + state = ACK0; + setTimeout(SENDER_TIMEOUT_MS, this); + unreliableSend(data_pkt, 0, data_pkt_len); + return data_pkt_len - 1; + + case S1: + System.out.print("S1:->ACK1 "); + blockSender(); + blockDisconnect(); + make_packet(1, buffer, offset, length); + state = ACK1; + setTimeout(SENDER_TIMEOUT_MS, this); + unreliableSend(data_pkt, 0, data_pkt_len); + return data_pkt_len - 1; + + case ACK0: + case ACK1: + case ACK0FIN: + case ACK1FIN: + case FIN: + default: + System.err.println("SAWSender.reliableSend: sender should be blocked. Ignoring request"); + return 0; + } + } + + private void goToFinState() { + System.out.print("->FIN "); + blockSender(); + blockDisconnect(); + make_packet(2, null, 0, 0); + state = FIN; + setTimeout(SENDER_TIMEOUT_MS, this); + unreliableSend(data_pkt, 0, data_pkt_len); + } + + public void close() { + switch (state) { + case S0: + System.out.print("S0:"); + goToFinState(); + return; + + case S1: + System.out.print("S1:"); + goToFinState(); + return; + + case ACK0: + System.out.print("ACK0:->ACK0FIN "); + state = ACK0FIN; + return; + + case ACK1: + System.out.print("ACK1:->ACK1FIN "); + state = ACK1FIN; + return; + + case FIN: + case CLOSED: + default: + System.err.println("SAWSender.closeConnection: connection already closed or in closing state. Ignoring request"); + return; + } + } +} diff --git a/hw2/transport.jar b/hw2/transport.jar new file mode 100644 index 0000000000000000000000000000000000000000..f4b2dbc95f41274e165a35b35c16d340074931cf GIT binary patch literal 8903 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 XRRzR{s}BQ%{_s10xb3a~GU@*TIy;j` literal 0 HcmV?d00001