The Selective Repeat ARQ (Automatic Repeat reQuest) protocol is a sliding window protocol that provides reliable data transfer over an unreliable network. Unlike Go-Back-N, Selective Repeat individually acknowledges and retransmits only the packets that are lost or corrupted, making it more efficient in networks with high error rates.
This implementation uses both ACK and NACK mechanisms: ACKs acknowledge successfully received packets, while NACKs explicitly request retransmission of missing packets.
static int lastack = 0; // Last cumulative ACK receivedstatic int window = 100; // Current window upper boundstatic List<DatagramPacket> packets = new ArrayList<>(); // Buffer for unACKed packets
State Transitions:
1
Initialization
Set lastack = 0 (no packets acknowledged)
Set window = 100 (initial window size)
Prepare packet buffer with all data packets
2
Send Initial Window
Send first min(totalPackets, windowSize) packets
Start timeout timer for each sent packet
Transition to “Waiting for ACKs” state
3
Process ACK/NACK
On ACK: Slide window forward, send new packets
On NACK: Retransmit specific packet immediately
On Timeout: Retransmit packet after timeout period
4
Completion
When lastack >= totalPackets, all packets acknowledged
Implementation in ReliableClientProtocol.java:187-219:
while (lastack < nofpackets + 1) { DatagramPacket ack = receivePacket(); Packet ackpacket = Packet.fromBytes(ack.getData()); // ACK received for packet within window if (ackpacket.getType() == 1 && // Type: ACK ackpacket.getSequenceNumber() > lastack && ackpacket.getSequenceNumber() <= window + 1) { // Calculate how many new packets can be sent int newacks = (int) ackpacket.getSequenceNumber() - lastack + 1; // Send new packets to fill the window if (packets.size() > window) { for (int i = window; i < window + newacks; i++) { if (packets.size() > i) { socket.send(packets.get(i)); // Start timer for new packet Thread t = new Thread(new Timer(i + 2, socket, 0)); t.start(); } } } // Slide the window window = window + newacks; lastack = (int) ackpacket.getSequenceNumber(); }}
The window only slides forward on cumulative ACKs, not on individual ACKs. The receiver sends cumulative ACKs for the highest in-order packet received.
A NACK with sequence number N explicitly requests retransmission of packet N
The sender immediately retransmits the requested packet
No window adjustment occurs
NACK Generation (Receiver):
// Packet arrived out of orderif (pSeq > expseqNum) { data.put(pSeq, p.getPayload()); // Buffer the packet // Send NACK for each missing packet for (i = expseqNum; i < pSeq; i++) { if (!data.containsKey(i)) { Packet nack = new Packet(4, // Type: NACK i, // Sequence of missing packet clientAddress, clientPort, new byte[0]); DatagramPacket nackP = new DatagramPacket(nack.toBytes(), nack.toBytes().length, routerAddress, routerPort); socket.send(nackP); } }}
public synchronized static void isAcked(int seq, DatagramSocket socket, int retries) throws IOException { // Check if packet has been acknowledged if (seq <= lastack) { // Packet already ACKed, do nothing return; } // Check if maximum retries reached if (retries >= 10) { // Give up after 10 attempts return; } // Retransmit the packet socket.send(packets.get(seq - 2)); // Start new timer with incremented retry counter Thread t = new Thread(new Timer(seq, socket, retries + 1)); t.start();}
Timeout Progression:
1
Initial Send (Retry 0)
Packet sent with 30ms timer started
2
First Timeout (Retry 1)
After 30ms, if no ACK: retransmit and start new 30ms timer
3
Subsequent Timeouts (Retry 2-9)
Continue retransmitting every 30ms up to 10 total attempts
4
Maximum Retries (Retry 10)
After 10 attempts (~300ms total), give up on packet
Fixed Timeout Issue: The implementation uses a fixed 30ms timeout without exponential backoff. In high-latency networks, this may cause premature retransmissions.
The protocol implements a TCP-like three-way handshake:Client Side (ReliableClientProtocol.java:22-74):
public static int connection(DatagramSocket socket, InetSocketAddress serverAddr) throws IOException { // 1. Send SYN packet Packet syn = new Packet.Builder() .setType(2) // SYN type .setSequenceNumber(1L) .setPortNumber(serverAddr.getPort()) .setPeerAddress(serverAddr.getAddress()) .setPayload("1".getBytes()) .create(); DatagramPacket synPacket = new DatagramPacket( syn.toBytes(), syn.toBytes().length, routerAddr.getAddress(), routerAddr.getPort()); socket.send(synPacket); // 2. Wait for SYN-ACK with retransmission socket.setSoTimeout(20); // 20ms timeout while (true) { try { DatagramPacket synAckPacket = new DatagramPacket(new byte[1024], 1024); socket.receive(synAckPacket); Packet synAck = Packet.fromBytes(synAckPacket.getData()); if (synAck.getType() == 3 && synAck.getSequenceNumber() == 1) { // SYN-ACK received break; } } catch (SocketTimeoutException e) { // Retransmit SYN socket.send(synPacket); } } // 3. Send ACK Packet ack = new Packet.Builder() .setType(1) // ACK type .setSequenceNumber(1L) .setPortNumber(serverAddr.getPort()) .setPeerAddress(serverAddr.getAddress()) .setPayload("11".getBytes()) .create(); DatagramPacket ackPacket = new DatagramPacket( ack.toBytes(), ack.toBytes().length, routerAddr.getAddress(), routerAddr.getPort()); socket.send(ackPacket); // Extract handler port from SYN-ACK payload String portStr = new String(synAck.getPayload(), StandardCharsets.UTF_8); return Integer.parseInt(portStr.trim());}
Server Side (ServerTcpProtocl.java:38-121):
public void handShake() throws IOException { while (true) { // 1. Receive SYN DatagramPacket request = new DatagramPacket(new byte[1024], 1024); connectionSocket.receive(request); Packet req = Packet.fromBytes(request.getData()); if (req.getType() != 2) { // Not a SYN continue; } InetAddress clientAddress = req.getPeerAddress(); // Create or reuse handler socket for this client DatagramSocket handlerSocket; if (socketMapping.containsKey(clientAddress)) { handlerSocket = socketMapping.get(clientAddress); } else { handlerSocket = new DatagramSocket(); // Random port socketMapping.put(clientAddress, handlerSocket); // Start handler thread new Thread(new clientHandler(handlerSocket, clientAddress)).start(); } // 2. Send SYN-ACK with handler port Packet synAck = new Packet( 3, // SYN-ACK type req.getSequenceNumber(), clientAddress, req.getPeerPort(), Integer.toString(handlerSocket.getLocalPort()).getBytes()); DatagramPacket response = new DatagramPacket( synAck.toBytes(), synAck.toBytes().length, request.getAddress(), request.getPort()); connectionSocket.send(response); // 3. Wait for ACK (with timeout) try { TimeoutBlock timeoutBlock = new TimeoutBlock(20); timeoutBlock.addBlock(() -> { while (true) { DatagramPacket ackPacket = new DatagramPacket(new byte[1024], 1024); connectionSocket.receive(ackPacket); Packet ack = Packet.fromBytes(ackPacket.getData()); if (ack.getType() == 1 && // ACK type ack.getPeerAddress().equals(clientAddress)) { break; // Connection established } } }); } catch (Throwable e) { // Timeout waiting for ACK (client will retry SYN) } }}
Port Allocation: The server returns a unique handler port in the SYN-ACK payload. All subsequent data transfer uses this handler port, not the main listening port (8000).
Both client and server use the same data transfer logic:
1
Fragment Data
Split data into 1013-byte chunks:
final int sizeMB = 1013;List<byte[]> chunks = IntStream.iterate(0, i -> i + sizeMB) .limit((datab.length + sizeMB - 1) / sizeMB) .mapToObj(i -> Arrays.copyOfRange(datab, i, Math.min(i + sizeMB, datab.length))) .collect(Collectors.toList());
2
Send Size Packet
First packet (seq=1) contains total number of packets:
Packet sizePacket = new Packet.Builder() .setType(0) // DATA type .setSequenceNumber(1L) .setPayload(Integer.toString(nofpackets + 1).getBytes()) .create();socket.send(sizePacket);// Wait for ACK before proceeding
3
Send Initial Window
Send first 100 packets (or fewer if less data):
int tobesent = Math.min(nofpackets, window);for (int i = 2; i <= tobesent + 1; i++) { socket.send(packets.get(i - 2)); Thread t = new Thread(new Timer(i, socket, 0)); t.start();}
When an out-of-order packet is received, NACKs are sent immediately for missing packets rather than waiting for timeout. This reduces retransmission delay from 30ms to ~1 RTT.
Each packet has its own timer thread, allowing individual packets to timeout and retransmit independently without affecting other packets in the window.