HTTP client with reliable UDP transport using Selective Repeat protocol
The client component implements a curl-like HTTP client (Httpc) that communicates over UDP using a reliable transport protocol (ReliableClientProtocol) based on Selective Repeat ARQ.
public class Httpc { // Sends an HTTP request and returns the response public Response sendRequest( int port, String server, String path, String type, boolean verbose, List headers, Object data ) // Parses command string and returns response public Response getResponse(String cmd) throws IOException // Returns help text for commands public String help(String option)}
The sendRequest method follows this sequence:
Create UDP socket
Build HTTP request string with method, path, headers, and body
Establish connection via ReliableClientProtocol.connection()
Send request via ReliableClientProtocol.requestresponse()
Client sends a packet with type=2 (SYN) and sequence number 1
client/ReliableClientProtocol.java
Packet p = new Packet.Builder() .setType(2) // SYN .setSequenceNumber(1L) .setPortNumber(serverAddr.getPort()) .setPeerAddress(serverAddr.getAddress()) .setPayload("1".getBytes()) .create();socket.send(packet);
2
Receive SYN-ACK
Server responds with type=3 (SYN-ACK) containing the dedicated port number
client/ReliableClientProtocol.java
socket.receive(synack);Packet synackpacket = Packet.fromBytes(synack.getData());if (synackpacket.getType() == 3 && synackpacket.getSequenceNumber() == 1) { // Extract server port from payload String serverport = new String(synackpacket.getPayload()); int sport = Integer.parseInt(serverport.trim());}
3
Send ACK
Client completes handshake with type=1 (ACK)
client/ReliableClientProtocol.java
p = new Packet.Builder() .setType(1) // ACK .setSequenceNumber(p.getSequenceNumber()) .setPortNumber(p.getPeerPort()) .setPeerAddress(p.getPeerAddress()) .setPayload("11".getBytes()) .create();socket.send(packet);
The requestresponse method handles the complete send/receive cycle:
client/ReliableClientProtocol.java
// 1. Split data into 1013-byte chunksfinal 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 packet count as first packet (seq=1)int nofpackets = chunks.size();Packet p = new Packet.Builder() .setType(0) .setSequenceNumber(1L) .setPayload(Integer.toString(nofpackets + 1).getBytes()) .create();socket.send(size);// 3. Wait for ACK of packet countsocket.receive(ack);// 4. Send all data packets using sliding windowsendreq(nofpackets, serverAddr, socket, chunks);// 5. Receive responseString res = response(socket);
The sendreq method implements the sliding window protocol:
client/ReliableClientProtocol.java
// Send initial window of packets (up to 100)int tobesent = Math.min(nofpackets, window);for (int i = 2; i <= tobesent + 1; i++) { socket.send(packets.get(i - 2)); // Start timer thread for retransmission Thread t = new Thread(new Timer(i, socket, 0)); t.start();}// Process ACKs and slide windowwhile (lastack < nofpackets + 1) { socket.receive(ack); Packet ackpacket = Packet.fromBytes(ack.getData()); if (ackpacket.getType() == 1) { // ACK int newacs = (int) ackpacket.getSequenceNumber() - lastack + 1; // Send new packets in window for (int i = window; i < window + newacs; i++) { socket.send(packets.get(i)); Thread t = new Thread(new Timer(i + 2, socket, 0)); t.start(); } window = window + newacs; lastack = (int) ackpacket.getSequenceNumber(); } if (ackpacket.getType() == 4) { // NACK // Retransmit specific packet socket.send(packets.get((int) ackpacket.getSequenceNumber() - 2)); }}
The getRequestFromPacket method receives packets using Selective Repeat:
client/ReliableClientProtocol.java
int expseqNum = 2; // Expected sequence numberint maxWindow = 100;HashMap<Integer, byte[]> data = new HashMap<>();while (expseqNum <= totalPackets) { socket.receive(req); Packet p = Packet.fromBytes(req.getData()); int pSeq = (int) p.getSequenceNumber(); if (pSeq == expseqNum) { // In-order packet: store and advance window data.put(expseqNum, p.getPayload()); expseqNum++; maxWindow++; // Send cumulative ACK Packet ack = new Packet(1, i-1, clientAddress, clientPort, new byte[0]); socket.send(ackP); } else if (pSeq > expseqNum) { // Out-of-order packet: store and send NACK for missing data.put(pSeq, p.getPayload()); for (i = expseqNum; i < pSeq; i++) { if (!data.containsKey(i)) { Packet nack = new Packet(4, i, clientAddress, clientPort, new byte[0]); socket.send(nackP); } } }}
public class Timer implements Runnable { int seq; // Sequence number int retries; // Retry count DatagramSocket socket; public void run() { Thread.sleep(30); // Wait 30ms ReliableClientProtocol.isAcked(seq, socket, retries); }}
client/ReliableClientProtocol.java
public synchronized static void isAcked(int seq, DatagramSocket socket, int retries) { if (seq <= lastack || retries >= 10) { // Packet acknowledged or max retries reached return; } // Retransmit packet socket.send(packets.get(seq - 2)); Thread t = new Thread(new Timer(seq, socket, retries + 1)); t.start();}
The protocol gives up after 10 retransmission attempts, which may result in incomplete data transfer over extremely lossy networks.
The Packet class encapsulates all network packets:
util/Packet.java
public class Packet { private final int type; // Packet type (0-6) private final long sequenceNumber; // Sequence number private final InetAddress peerAddress; // Peer IP address private final int peerPort; // Peer port private final byte[] payload; // Packet payload (max 1013 bytes) public static final int MIN_LEN = 11; // Minimum packet size public static final int MAX_LEN = 11 + 1024; // Maximum packet size}