Skip to main content
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.

Architecture Overview

The client consists of two main classes that work together:

Httpc

Command-line HTTP client that parses user commands and constructs HTTP requests

ReliableClientProtocol

Implements Selective Repeat ARQ protocol for reliable data transfer over UDP

Httpc Class

The Httpc class provides a curl-like interface for making HTTP requests over the reliable UDP transport.

Key Responsibilities

  • Parse command-line arguments (GET/POST, headers, data, files)
  • Construct HTTP requests following the HTTPFC/1.0 protocol
  • Coordinate with ReliableClientProtocol for reliable transmission
  • Parse and format HTTP responses

Class Structure

client/Httpc.java
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)
}

HTTP Request Format

String req = "GET " + path + " HTTPFC/1.0" + System.lineSeparator();
for (Object header : headers) {
    req = req + header.toString() + System.lineSeparator();
}

Command-Line Interface

The client supports curl-like commands:
httpc get [-v] [-h key:value] URL
Options:
  • -v - Verbose mode (prints headers)
  • -h key:value - Add HTTP headers
Example:
httpc get -v -h "User-Agent:MyClient" http://example.com:8000/file.txt
The client uses the custom protocol HTTPFC/1.0 (HTTP over Flow Control) instead of standard HTTP.

ReliableClientProtocol Class

Implements the Selective Repeat ARQ protocol for reliable data transfer over unreliable UDP channels.

Protocol Features

Selective Repeat

Individual acknowledgment of packets with selective retransmission

Sliding Window

Window size of 100 packets for efficient throughput

Three-Way Handshake

SYN, SYN-ACK, ACK connection establishment

Packet Chunking

Splits data into 1013-byte chunks for transmission

Class Structure

client/ReliableClientProtocol.java
public class ReliableClientProtocol {
    static int lastack = 0;        // Last acknowledged packet
    static int window = 100;        // Current window size
    static List<DatagramPacket> packets = new ArrayList<>();
    static InetSocketAddress routerAddr = new InetSocketAddress("127.0.0.1", 3000);
    
    // Establishes three-way handshake connection
    public static int connection(DatagramSocket socket, InetSocketAddress serverAddr)
    
    // Sends request and receives response
    public static String requestresponse(DatagramSocket socket, 
                                         InetSocketAddress serverAddr, 
                                         String data)
    
    // Sends request packets using sliding window
    public static void sendreq(int nofpackets, 
                               InetSocketAddress serverAddr, 
                               DatagramSocket socket, 
                               List<byte[]> chunks)
    
    // Timer callback for retransmission
    public synchronized static void isAcked(int seq, 
                                           DatagramSocket socket, 
                                           int retries)
    
    // Receives and reassembles response packets
    static String response(DatagramSocket socket)
}

Connection Establishment

The three-way handshake process:
1

Send SYN

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);

Data Transmission Flow

The requestresponse method handles the complete send/receive cycle:
client/ReliableClientProtocol.java
// 1. 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 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 count
socket.receive(ack);

// 4. Send all data packets using sliding window
sendreq(nofpackets, serverAddr, socket, chunks);

// 5. Receive response
String res = response(socket);

Timeout and Retransmission

The Timer class handles packet timeouts:
util/Timer.java
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.

Packet Structure

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
}

Packet Types

TypeNameDescription
0DATAData packet containing payload
1ACKAcknowledgment packet
2SYNSynchronization packet (connection request)
3SYN-ACKSynchronization acknowledgment
4NACKNegative acknowledgment (request retransmission)
6FINFinish packet (close connection)

Wire Format

Packets are serialized in BigEndian format:
+--------+----------------+----------------+-----------+-----------+
| Type   | Sequence Num   | Peer Address   | Peer Port | Payload   |
| 1 byte | 4 bytes        | 4 bytes        | 2 bytes   | 0-1013 B  |
+--------+----------------+----------------+-----------+-----------+
public byte[] toBytes() {
    ByteBuffer buf = ByteBuffer.allocate(MAX_LEN).order(ByteOrder.BIG_ENDIAN);
    buf.put((byte) type);
    buf.putInt((int) sequenceNumber);
    buf.put(peerAddress.getAddress());
    buf.putShort((short) peerPort);
    buf.put(payload);
    buf.flip();
    byte[] raw = new byte[buf.remaining()];
    buf.get(raw);
    return raw;
}

Usage Example

1

Create Client Instance

Httpc client = new Httpc();
2

Build Command String

String command = "httpc get -v http://localhost:8000/file.txt";
3

Get Response

Response response = client.getResponse(command);
System.out.println(response.toString());

Configuration

routerAddr
InetSocketAddress
default:"127.0.0.1:3000"
The address of the packet router that forwards packets between client and server
window
int
default:"100"
The sliding window size for the Selective Repeat protocol
chunkSize
int
default:"1013"
The maximum payload size per packet in bytes
timeout
int
default:"30ms"
The timeout duration before packet retransmission
maxRetries
int
default:"10"
Maximum number of retransmission attempts before giving up

Response Object

The Response class encapsulates HTTP responses:
util/Response.java
public class Response {
    private String status;    // HTTP status text ("OK", "Not Found", etc.)
    private String code;      // HTTP status code ("200", "404", etc.)
    private String header;    // Response headers (null if not verbose)
    private String body;      // Response body
    private boolean inFile;   // Whether to write to file
    private String file;      // Output file path
    private String error;     // Error message if any
}
In non-verbose mode, only the response body is returned. Use the -v flag to see headers and status information.

Build docs developers (and LLMs) love