0

I am trying to create a TFTP client using java NIO. I am able to receive first 512 bytes of data from server, but not able to send acknowledgement to server for getting next block of packet. I am new to java NIO and networking. Not able to find solution for it. So can anyone help me on this to find fix for it? Thanks in advance

package app.sdc.business;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;

import app.sdc.business.NetworkElementPool.NetworkElement;

public class TftpNioClient {

static byte OP_ERROR = 5, OP_DATAPACKET = 3, OP_ACK = 4, OP_RRQ = 1, OP_WRQ = 2;

static final String LOCALHOST = "localhost";

static InetSocketAddress server = new InetSocketAddress(LOCALHOST, 69);

// main method
public static void main(String[] args) throws IOException {
    processDownload();
}

// Will start downloading of all files
public static void processDownload() throws IOException {
    Selector sel = Selector.open();
    for (NetworkElement ne : NetworkElementPool.getNetworkElements()) {
        DatagramChannel channel = DatagramChannel.open();
        channel.configureBlocking(false);
        channel.connect(server);
        channel.register(sel, SelectionKey.OP_READ | SelectionKey.OP_WRITE, ne);
    }

    int counter = 0;
    while (true) {
        int n = sel.select(3000);
        if (n < 1) {
            continue;
        }

        Iterator<SelectionKey> itr = sel.selectedKeys().iterator();
        while (itr.hasNext()) {
            SelectionKey key = itr.next();
            itr.remove();

            if (key.isWritable()) {
                counter++;
                System.out.println("channel Write...");
                downloadUsingTFTPProtocol(key);
            } else if (key.isReadable()) {
                System.out.println("Channel Read");
            }

        }

        if (counter >= NetworkElementPool.getNetworkElements().size()) {
            break;
        }
    }
}

// method for downloading file
private static void downloadUsingTFTPProtocol(SelectionKey keyy) throws IOException {
    ByteArrayOutputStream byteOutOS = new ByteArrayOutputStream();
    Selector sel = keyy.selector();
    NetworkElement ne = (NetworkElement) keyy.attachment();
    String fileName = ne.getFilename();

    DatagramChannel channel = DatagramChannel.open();
    channel.configureBlocking(false);
    channel.register(sel, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

    boolean reqSent = false;
    ByteBuffer sendBuffer = null;
    ByteBuffer receivedBuffer = ByteBuffer.allocate(516);

    boolean stop = false;
    byte[] dataByte = null;
    boolean received = false;

    outer: while (true) {
        int n = sel.select();
        if (n < 1) {
            continue;
        }

        Iterator<SelectionKey> itr = sel.selectedKeys().iterator();

        while (itr.hasNext()) {
            SelectionKey key = itr.next();
            itr.remove();

            DatagramChannel dc = (DatagramChannel) key.channel();

            if (!received && key.isReadable()) {
                System.out.println("receive packet...");

                receivedBuffer.clear();
                dc.receive(receivedBuffer);
                stop = receivedBuffer.position() < 512;

                receivedBuffer.flip();
                while (receivedBuffer.hasRemaining()) {
                    System.out.print(receivedBuffer.get());
                }
                System.out.println();

                dataByte = receivedBuffer.array();
                received = true;
            }

            if (key.isWritable()) {
                if (!reqSent) {
                    System.out.println("Sending First Request....");
                    sendBuffer = createInitialReadRequest("SDCSource/" + fileName);
                    sendBuffer.flip();
                    dc.send(sendBuffer, server);
                    reqSent = true;
                } else if (received) {
                    System.out.println("Send Acknowledgement");
                    byte[] opCode = new byte[] { dataByte[0], dataByte[1] };

                    if (opCode[1] == OP_ERROR) {
                        System.out.println("Error Occured...");
                        break outer;
                    } else if (opCode[1] == OP_DATAPACKET) {
                        byte[] blockNumber = { dataByte[2], dataByte[3] };

                        sendBuffer = getAcknowledgment(blockNumber, dc, server);
                        sendBuffer.flip();
                        dc.send(sendBuffer, server);

                        DataOutputStream dos = new DataOutputStream(byteOutOS);
                        dos.write(dataByte, 4, dataByte.length - 4);
                    }
                }
                received = false;
            }

            if (stop) {
                break outer;
            }
        }
    }

    writeFile(byteOutOS, fileName);
}

// Creates request packet to send request at the beginning
private static ByteBuffer createInitialReadRequest(final String fileName) {
    String mode = "octet";
    int rrqByteLength = 2 + fileName.getBytes().length + 1 + mode.getBytes().length + 1;
    byte[] rrqByteArray = new byte[rrqByteLength];

    ByteBuffer reqBuf = ByteBuffer.allocate(rrqByteArray.length);

    reqBuf.put((byte) 0).put((byte) OP_RRQ);
    reqBuf.put(fileName.getBytes());
    reqBuf.put((byte) 0);
    reqBuf.put(mode.getBytes());
    reqBuf.put((byte) 0);

    return reqBuf;
}

// Creating acknowledgement code
private static ByteBuffer getAcknowledgment(byte[] blockNumber, DatagramChannel channel, InetSocketAddress server)
        throws IOException {

    byte[] acknowledge = { 0, OP_ACK, blockNumber[0], blockNumber[1] };
    ByteBuffer buffer = ByteBuffer.allocate(acknowledge.length);
    buffer.put(acknowledge);
    return buffer;
}

// Create file after all packets have been received
private static void writeFile(ByteArrayOutputStream baoStream, String fileName) {
    try {
        OutputStream outputStream = new FileOutputStream("SDCTarget/" + fileName);
        baoStream.writeTo(outputStream);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}


package app.sdc.business;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class NetworkElementPool {

private static List<NetworkElement> networkElements;

// create a list of network elements by reading files downloading. It is just a hard-coded way to create network elements
static {
    networkElements = new ArrayList<NetworkElement>();
    File sourceDir = new File("C:/OpenTFTPServer/SDCSource");
    if (sourceDir.exists()) {
        for (String filename : sourceDir.list()) {
            networkElements.add(new NetworkElement("localhost", 8080, filename));
        }
    } else {
        System.err.println("Network Elements couldn't found...");
    }
}

public static List<NetworkElement> getNetworkElements() {
    return networkElements;
}

// Represents a network element
public static class NetworkElement {
    private String host;
    private int port;
    private String filename;

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public String getFilename() {
        return filename;
    }

    public NetworkElement() {
        super();
    }

    public NetworkElement(String host, int port, String filename) {
        super();
        this.host = host;
        this.port = port;
        this.filename = filename;
    }

    @Override
    public String toString() {
        return "NetworkElement [host=" + host + ", port=" + port + ",  filename=" + filename + "]";
    }

}
}

Note: TftpClient class contain one foreach loop to start downloading file from more than one network element.

sanit
  • 1,646
  • 1
  • 18
  • 21

2 Answers2

0

You're not sending the acknowledgements at the correct time, after a read: you're sending them every time around the loop. You don't need to wait for OP_WRITE to do a write. Just write whenever you need to. You shouldn't even register for OP_WRITE most of the time. See this answer for the correct technique. But in the case of TFTP it's dubious whether you need that at all. Or NIO either.

Community
  • 1
  • 1
user207421
  • 305,947
  • 44
  • 307
  • 483
0

In your code, if the key.isReadable, then you can read the packet. Then immediately followed by that you can send the ACK back to TFTP server.

} else if (received) {

This block of code can go to the line immediately next to dc.receive(receivedBuffer);

You can refer this example java nio tftp client

Joseph
  • 877
  • 8
  • 20