Skip to main content
The server component implements an HTTP file server that communicates over UDP using a reliable transport protocol. It consists of ServerTcpProtocl for connection management and HttpcLib for HTTP request processing.

Architecture Overview

ServerTcpProtocl

Manages UDP connections, handshakes, and reliable data transfer using Selective Repeat ARQ

HttpcLib

Processes HTTP GET and POST requests, handles file operations and response generation

ServerTcpProtocl Class

The main server class that handles UDP connection management and reliable packet transmission.

Key Responsibilities

  • Listen for incoming connection requests on main port
  • Perform three-way handshake with clients
  • Create dedicated socket and handler thread for each client
  • Implement Selective Repeat protocol for reliable data transfer
  • Manage multiple concurrent client connections

Class Structure

Server/ServerTcpProtocl.java
public class ServerTcpProtocl {
    private DatagramSocket connectionSocket;  // Main listening socket
    private static HashMap<InetAddress, DatagramSocket> socketMapping;  // Per-client sockets
    private static String directory;          // Server root directory
    private static boolean verbose;           // Logging flag
    
    private static int lastackRec = 0;        // Last acknowledged packet
    private static int windowRec = 100;       // Sliding window size
    private static List<DatagramPacket> packets = new ArrayList<>();
    
    public ServerTcpProtocl(int port, String directory, boolean verbose)
    public void handShake() throws IOException
    public static class clientHandler implements Runnable
}

Initialization

The server is initialized with port, directory, and verbosity settings:
Server/ServerTcpProtocl.java
public ServerTcpProtocl(int port, String directory, boolean verbose) throws IOException {
    this.connectionSocket = new DatagramSocket(port);
    socketMapping = new HashMap<>();
    ServerTcpProtocl.directory = directory;
    ServerTcpProtocl.verbose = verbose;
    handShake();  // Start listening for connections
}
ServerTcpProtocl server = new ServerTcpProtocl(
    8000,        // Port
    "./files",   // Root directory
    true         // Verbose logging
);

Connection Establishment

The server implements a three-way handshake protocol:
1

Receive SYN

Server listens for SYN packets (type=2) on the main connection socket
Server/ServerTcpProtocl.java
DatagramPacket request = new DatagramPacket(new byte[1024], 1024);
this.connectionSocket.receive(request);
Packet req = Packet.fromBytes(request.getData());

if (req.getType() == 2) {  // SYN packet
    InetAddress clientAddress = req.getPeerAddress();
    int clientPort = req.getPeerPort();
    // Process connection request...
}
2

Create Client Handler

For each new client, create a dedicated socket and handler thread
Server/ServerTcpProtocl.java
DatagramSocket datasocket;
if (socketMapping.containsKey(clientAddress)) {
    datasocket = socketMapping.get(clientAddress);
} else {
    datasocket = new DatagramSocket();  // Random port
    socketMapping.put(clientAddress, datasocket);
    
    if (verbose) {
        System.out.println("Client handler created for " + clientAddress);
    }
    
    // Start dedicated handler thread
    new Thread(new clientHandler(datasocket, clientAddress)).start();
}
3

Send SYN-ACK

Respond with SYN-ACK containing the dedicated port number
Server/ServerTcpProtocl.java
Packet p = new Packet(
    3,  // SYN-ACK type
    req.getSequenceNumber(),
    clientAddress,
    clientPort,
    Integer.toString(datasocket.getLocalPort()).getBytes()  // Port number in payload
);
DatagramPacket response = new DatagramPacket(
    p.toBytes(), 
    p.toBytes().length, 
    request.getAddress(),  // Router address
    request.getPort()       // Router port
);
this.connectionSocket.send(response);
4

Wait for ACK

Wait for final ACK (type=1) with timeout
Server/ServerTcpProtocl.java
TimeoutBlock timeoutBlock = new TimeoutBlock(20);  // 20ms timeout
Runnable block = new Runnable() {
    @Override
    public void run() {
        while (true) {
            connectionSocket.receive(request);
            Packet req2 = Packet.fromBytes(request.getData());
            
            if (req2.getType() == 1 &&  // ACK
                req2.getPeerAddress().equals(clientAddress) &&
                req2.getPeerPort() == clientPort) {
                break;  // Handshake complete
            }
        }
    }
};
timeoutBlock.addBlock(block);
Each client gets a dedicated UDP socket and handler thread, allowing concurrent connections while maintaining isolation between clients.

Client Handler

The clientHandler inner class processes requests for a single client connection.

Handler Lifecycle

The handler receives the request using Selective Repeat protocol:
Server/ServerTcpProtocl.java
@Override
public void run() {
    DatagramPacket req = new DatagramPacket(new byte[1024], 1024);
    
    // Wait for first packet with total packet count
    socket.receive(req);
    Packet p = Packet.fromBytes(req.getData());
    
    if (p.getSequenceNumber() == 1 && p.getType() == 0) {
        String total = new String(p.getPayload());
        
        // Send ACK for packet count
        Packet ack = new Packet(1, 1L, p.getPeerAddress(), p.getPeerPort(), new byte[0]);
        DatagramPacket ackP = new DatagramPacket(
            ack.toBytes(), 
            ack.toBytes().length, 
            req.getAddress(), 
            req.getPort()
        );
        socket.send(ackP);
        
        // Receive all request packets
        request = getRequestFromPacket(socket, Integer.parseInt(total.trim()));
    }
}

Receiving Packets (Selective Repeat)

The getRequestFromPacket method implements the receiver side of Selective Repeat:
Server/ServerTcpProtocl.java
public String getRequestFromPacket(DatagramSocket socket, int totalPackets) {
    int expseqNum = 2;        // Expected sequence number (starting after packet count)
    int maxWindow = 100;       // Maximum window size
    HashMap<Integer, byte[]> data = new HashMap<>();  // Buffer for out-of-order packets
    
    while (expseqNum <= totalPackets) {
        DatagramPacket req = new DatagramPacket(new byte[1024], 1024);
        socket.receive(req);
        Packet p = Packet.fromBytes(req.getData());
        int pSeq = (int) p.getSequenceNumber();
        
        if (pSeq <= maxWindow) {  // Within window
            if (pSeq == expseqNum) {
                // In-order packet
                data.put(expseqNum, p.getPayload());
                
                // Check for consecutive packets in buffer
                for (int i = expseqNum; i <= totalPackets; i++) {
                    if (data.containsKey(i)) {
                        expseqNum++;
                        maxWindow++;
                    } else {
                        // Send cumulative ACK
                        Packet ack = new Packet(1, i-1, clientAddress, clientPort, new byte[0]);
                        socket.send(new DatagramPacket(ack.toBytes(), ack.toBytes().length, 
                                                       rAddress, rPort));
                        break;
                    }
                }
            } 
            else if (pSeq > expseqNum) {
                // Out-of-order packet (future packet)
                data.put(pSeq, p.getPayload());
                
                // Send NACK for missing packets
                for (int i = expseqNum; i < pSeq; i++) {
                    if (!data.containsKey(i)) {
                        Packet nack = new Packet(4, i, clientAddress, clientPort, new byte[0]);
                        socket.send(new DatagramPacket(nack.toBytes(), nack.toBytes().length,
                                                       rAddress, rPort));
                    }
                }
            }
            else if (pSeq < expseqNum) {
                // Duplicate packet (already received)
                Packet ack = new Packet(1, pSeq, clientAddress, clientPort, new byte[0]);
                socket.send(new DatagramPacket(ack.toBytes(), ack.toBytes().length,
                                               rAddress, rPort));
            }
        }
    }
    
    // Reassemble data from chunks
    byte[] reqArr = new byte[(totalPackets - 1) * 1013];
    int k = 0;
    for (int i = 2; i <= totalPackets; i++) {
        byte[] tmp = data.get(i);
        for (int j = 0; j < tmp.length; j++) {
            reqArr[k++] = tmp[j];
        }
    }
    
    return new String(reqArr).trim();
}
The receiver only accepts packets within the current window (expseqNum to maxWindow). Packets outside this range are silently discarded.

Sending Response Packets

The requestresponse and sendreq methods handle response transmission:
public static void requestresponse(DatagramSocket socket, 
                                   InetSocketAddress serverAddr,
                                   InetSocketAddress routerAddr, 
                                   String data) throws IOException {
    byte[] datab = data.getBytes();
    
    // Split 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());
    
    // Send packet count
    int nofpackets = chunks.size();
    Packet p = new Packet.Builder()
        .setType(0)
        .setSequenceNumber(1L)
        .setPortNumber(serverAddr.getPort())
        .setPeerAddress(serverAddr.getAddress())
        .setPayload(Integer.toString(nofpackets + 1).getBytes())
        .create();
    socket.send(size);
    
    // Wait for ACK with timeout and retransmission
    socket.setSoTimeout(30);
    while (true) {
        try {
            socket.receive(ack);
            Packet ackpacket = Packet.fromBytes(ack.getData());
            if (ackpacket.getType() == 1 && ackpacket.getSequenceNumber() == 1) {
                break;
            }
        } catch (SocketTimeoutException s) {
            socket.send(size);  // Retransmit
        }
    }
    
    socket.setSoTimeout(0);
    sendreq(nofpackets, serverAddr, routerAddr, socket, chunks);
}

HttpcLib Class

The HttpcLib class processes HTTP requests and generates responses.

GET Request Processing

Handle GET requests with query parameters:
Server/HttpcLib.java
if (path.startsWith("/get?")) {
    path = path.substring(5);  // Remove "/get?"
    String[] var = path.split("&");
    
    for (int i = 0; i < var.length; i++) {
        data = data + var[i] + "\n";
    }
    
    status = "OK";
    code = "200";
}

Security: Path Validation

The checkPath method prevents directory traversal attacks:
Server/HttpcLib.java
static boolean checkPath(String path, String dir) throws IOException {
    File f = new File(String.valueOf(Paths.get(dir + path)));
    String c = f.getCanonicalPath();  // Resolve .. and symlinks
    
    File f1 = new File(dir);
    String d = f1.getCanonicalPath();
    
    // Ensure requested path is within server directory
    if (c.startsWith(d)) {
        return true;
    } else {
        return false;  // Block access outside root directory
    }
}
This security check prevents clients from accessing files outside the server’s root directory using paths like /../../../etc/passwd.

POST Request Processing

static String postResponse(String req, String directory, boolean verbose) {
    String data = "", status, code;
    
    BufferedReader reader = new BufferedReader(new StringReader(req));
    
    // Parse header line to get filename
    String header = reader.readLine();
    String filename = header.split(" ")[1];
    
    // Skip headers until empty line
    while (header.length() > 0) {
        header = reader.readLine();
    }
    
    // Read body
    String bodyLine = reader.readLine();
    while (bodyLine != null) {
        data = data + bodyLine.trim() + "\n";
        bodyLine = reader.readLine();
    }
    data = data.trim();
}

HTTP Response Format

Both GET and POST responses follow the same format:
Server/HttpcLib.java
String headers = "Httfc/1.0 " + code + " " + status + System.lineSeparator() +
                 "Date :" + java.time.LocalDate.now() + System.lineSeparator() +
                 "Server : Httpfc/1.0.0" + System.lineSeparator() +
                 "Content-Length: " + data.length() + System.lineSeparator() +
                 "Connection: Closed" + System.lineSeparator() +
                 "Content-type: Application/text";

return headers + "\n\n" + data;

Error Handling

404 Not Found

Returned when requested file doesn’t exist or path validation fails
status = "File not found";
code = "404";

400 Bad Request

Returned when POST request doesn’t specify filename
status = "BAD REQUEST";
code = "400";

500 Internal Server Error

Returned on unexpected errors during request processing
status = "INTERNAL SERVER ERROR";
code = "500";

504 Gateway Timeout

Returned on general GET request errors
status = "Internal Server Error";
code = "504";

Configuration Parameters

port
int
default:"8000"
The UDP port number to listen on for incoming connections
directory
String
default:"."
Root directory for file operations. Files outside this directory cannot be accessed.
verbose
boolean
default:"false"
Enable detailed logging of connection handling and packet transmission
windowRec
int
default:"100"
Sliding window size for receiving and sending packets
chunkSize
int
default:"1013"
Maximum payload size per packet in bytes
timeout
int
default:"30ms"
Socket timeout for waiting for acknowledgments

Concurrency Model

The server uses a thread-per-client model:
┌─────────────────────────────────────────┐
│  Main Thread (handShake)                │
│  - Listen on port 8000                  │
│  - Accept SYN packets                   │
│  - Create client handlers               │
└────────┬────────────────────────────────┘

         ├─────> Client Handler Thread 1 (192.168.1.10)
         │       - Dedicated socket (random port)
         │       - Receive request
         │       - Process HTTP
         │       - Send response

         ├─────> Client Handler Thread 2 (192.168.1.11)
         │       - Independent socket
         │       - Concurrent execution

         └─────> Client Handler Thread N
Each client gets an isolated socket and thread, preventing interference between concurrent connections. The main thread continues accepting new connections while handlers process existing ones.

Usage Example

Server/ServerTcpProtocl.java
public static void main(String[] args) throws IOException {
    ServerTcpProtocl server = new ServerTcpProtocl(
        8000,       // Listen on port 8000
        "./data",   // Serve files from ./data directory
        true        // Enable verbose logging
    );
    // Server runs indefinitely, handling client connections
}

Build docs developers (and LLMs) love