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.