├── .gitignore ├── README.md ├── UnreliNET ├── UnreliNET$ReorderPkt.class ├── UnreliNET$UnreliThreadProcess.class └── UnreliNET.class └── src ├── Constants.java ├── FileReceiver.java ├── FileSender.java ├── ResponseHandlingThread.java └── Segment.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | !UnreliNET*.class 3 | 4 | # Mobile Tools for Java (J2ME) 5 | .mtj.tmp/ 6 | 7 | # Package Files # 8 | *.jar 9 | *.war 10 | *.ear 11 | 12 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 13 | hs_err_pid* 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReliableFileTransferProtocol 2 | 3 | Simple sending and receiving transport level code for implementing a reliable file transfer protocol over UDP. The underlying channel is unreliable and may corrupt, drop or even re-order the packets at random. 4 | 5 | ### Speed 6 | **First Place** in CS2105 (Introduction to Computer Networks) Speed Contest AY15/16 Sem1. 7 | 8 | (1.835s for 50MB, 2% dropping rate, 2% corruption rate, on grader's machine) 9 | 10 | Note that some code are specially tuned for the speed contest and thus do not follow good practices. 11 | -------------------------------------------------------------------------------- /UnreliNET/UnreliNET$ReorderPkt.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinan/ReliableFileTransferProtocol/ce79a4cb087b1fe7acd0ada0a53e39e259963a3d/UnreliNET/UnreliNET$ReorderPkt.class -------------------------------------------------------------------------------- /UnreliNET/UnreliNET$UnreliThreadProcess.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinan/ReliableFileTransferProtocol/ce79a4cb087b1fe7acd0ada0a53e39e259963a3d/UnreliNET/UnreliNET$UnreliThreadProcess.class -------------------------------------------------------------------------------- /UnreliNET/UnreliNET.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinan/ReliableFileTransferProtocol/ce79a4cb087b1fe7acd0ada0a53e39e259963a3d/UnreliNET/UnreliNET.class -------------------------------------------------------------------------------- /src/Constants.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Xinan on 4/10/15. 3 | */ 4 | public class Constants { 5 | public final static int segmentSize = 1000; 6 | public final static int headerSize = 8; 7 | public final static int chunkSize = 992; 8 | } 9 | -------------------------------------------------------------------------------- /src/FileReceiver.java: -------------------------------------------------------------------------------- 1 | import java.io.File; 2 | import java.io.RandomAccessFile; 3 | import java.net.DatagramPacket; 4 | import java.net.DatagramSocket; 5 | import java.net.SocketAddress; 6 | 7 | /** 8 | * Created by Xinan on 4/10/15. 9 | */ 10 | public class FileReceiver { 11 | 12 | static DatagramSocket socket; 13 | static SocketAddress senderAddr; 14 | 15 | public static void main(String[] args) throws Exception { 16 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 17 | int port = Integer.parseInt(args[0]); 18 | 19 | socket = new DatagramSocket(port); 20 | 21 | File file; 22 | RandomAccessFile out; 23 | int numChunks; 24 | long fileLength; 25 | 26 | byte[] buffer = new byte[Constants.segmentSize]; 27 | DatagramPacket packet = new DatagramPacket(buffer, Constants.segmentSize); 28 | Segment segment; 29 | boolean[] received; 30 | int index; 31 | 32 | while (true) { 33 | while (true) { 34 | socket.receive(packet); 35 | segment = new Segment(packet, true); 36 | if (segment.isMetadata() && segment.isValid()) { 37 | fileLength = segment.getFileLength(); 38 | numChunks = (int) Math.ceil(1F * fileLength / Constants.chunkSize); 39 | senderAddr = packet.getSocketAddress(); 40 | file = new File(segment.getFileName()); 41 | file.getAbsoluteFile().getParentFile().mkdirs(); 42 | out = new RandomAccessFile(file, "rw"); 43 | sendAck(segment.getSequenceNumber()); 44 | break; 45 | } 46 | } 47 | 48 | received = new boolean[numChunks]; 49 | while (numChunks > 0) { 50 | socket.receive(packet); 51 | segment = new Segment(packet); 52 | if (segment.isValid()) { 53 | if (!segment.isMetadata()) { 54 | index = segment.getSequenceNumber() - 1; 55 | if (!received[index]) { 56 | out.seek(index * Constants.chunkSize); 57 | out.write(segment.getData(), 0, segment.getDataLength()); 58 | received[index] = true; 59 | numChunks--; 60 | } 61 | } 62 | sendAck(segment.getSequenceNumber()); 63 | } 64 | } 65 | out.setLength(fileLength); 66 | for (int i = 0; i < 1000; i++) { 67 | sendAck(-1); 68 | } 69 | out.close(); 70 | } 71 | } 72 | 73 | private static void sendAck(int sequenceNumber) throws Exception { 74 | Segment segment = new Segment(sequenceNumber, new byte[0]); 75 | DatagramPacket packet = new DatagramPacket(segment.getBytes(), segment.length(), senderAddr); 76 | socket.send(packet); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/FileSender.java: -------------------------------------------------------------------------------- 1 | import java.io.File; 2 | import java.io.RandomAccessFile; 3 | import java.net.DatagramPacket; 4 | import java.net.DatagramSocket; 5 | import java.net.InetSocketAddress; 6 | import java.net.SocketAddress; 7 | import java.nio.ByteBuffer; 8 | 9 | /** 10 | * Created by Xinan on 4/10/15. 11 | */ 12 | public class FileSender { 13 | 14 | public static void main(String[] args) throws Exception { 15 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 16 | 17 | String host = args[0]; 18 | int port = Integer.parseInt(args[1]); 19 | String src = args[2]; 20 | String dest = args[3]; 21 | 22 | SocketAddress receiverAddr = new InetSocketAddress(host, port); 23 | DatagramSocket socket = new DatagramSocket(); 24 | DatagramPacket packet; 25 | 26 | RandomAccessFile in = new RandomAccessFile(new File(src), "r"); 27 | 28 | int numChunks = (int) Math.ceil(1F * in.length() / Constants.chunkSize); 29 | 30 | boolean[] received = new boolean[numChunks + 1]; 31 | ResponseHandlingThread responseHandlingThread = (new ResponseHandlingThread(received, numChunks, socket)); 32 | responseHandlingThread.setPriority(Thread.MAX_PRIORITY); 33 | responseHandlingThread.start(); 34 | 35 | boolean completed = false; 36 | Segment segment; 37 | byte[] buffer = new byte[Constants.chunkSize]; 38 | 39 | while (!received[0]) { 40 | segment = getMetadata(in.length(), dest); 41 | packet = new DatagramPacket(segment.getBytes(), segment.length(), receiverAddr); 42 | socket.send(packet); 43 | } 44 | 45 | while (!completed) { 46 | completed = true; 47 | for (int i = 1; i <= numChunks; i++) { 48 | if (received[i]) { 49 | continue; 50 | } 51 | completed = false; 52 | in.seek((i - 1) * Constants.chunkSize); 53 | in.read(buffer, 0, Constants.chunkSize); 54 | segment = new Segment(i, buffer); 55 | packet = new DatagramPacket(segment.getBytes(), segment.length(), receiverAddr); 56 | socket.send(packet); 57 | } 58 | } 59 | in.close(); 60 | socket.close(); 61 | } 62 | 63 | public static Segment getMetadata(long fileLength, String fileName) { 64 | ByteBuffer buffer = ByteBuffer.allocate(Constants.chunkSize); 65 | buffer.putLong(fileLength); 66 | buffer.putInt(fileName.length()); 67 | buffer.put(fileName.getBytes()); 68 | return new Segment(0, buffer.array()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/ResponseHandlingThread.java: -------------------------------------------------------------------------------- 1 | import java.net.DatagramPacket; 2 | import java.net.DatagramSocket; 3 | 4 | /** 5 | * Created by Xinan on 4/10/15. 6 | */ 7 | public class ResponseHandlingThread extends Thread { 8 | private final boolean[] received; 9 | private int numChunks; 10 | private DatagramSocket socket; 11 | 12 | public ResponseHandlingThread(boolean[] received, int numChunks, DatagramSocket socket) { 13 | this.received = received; 14 | this.numChunks = numChunks; 15 | this.socket = socket; 16 | } 17 | 18 | public void run() { 19 | byte[] buffer = new byte[Constants.headerSize]; 20 | Segment segment; 21 | DatagramPacket packet = new DatagramPacket(buffer, Constants.headerSize); 22 | while (numChunks >= 0) { 23 | try { 24 | socket.receive(packet); 25 | } catch (Exception e) { 26 | e.printStackTrace(); 27 | } 28 | segment = new Segment(packet); 29 | if (segment.isValid() && segment.getSequenceNumber() == -1) { 30 | for (int i = 0; i < received.length; i++) { 31 | received[i] = true; 32 | } 33 | return; 34 | } 35 | if (segment.isValid() && !received[segment.getSequenceNumber()]) { 36 | received[segment.getSequenceNumber()] = true; 37 | numChunks--; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Segment.java: -------------------------------------------------------------------------------- 1 | import java.net.DatagramPacket; 2 | import java.nio.ByteBuffer; 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Created by Xinan on 4/10/15. 7 | */ 8 | public class Segment { 9 | private int checksum; 10 | private int sequenceNumber; 11 | private byte[] data; 12 | 13 | private long fileLength = -1; 14 | private String fileName = null; 15 | 16 | public Segment(int sequenceNumber, byte[] data) { 17 | this.sequenceNumber = sequenceNumber; 18 | this.data = data; 19 | checksum = -sequenceNumber; 20 | for (byte b : data) { 21 | checksum -= b; 22 | } 23 | } 24 | 25 | public Segment(DatagramPacket packet, boolean expectMeta) { 26 | byte[] data = packet.getData(); 27 | ByteBuffer buffer = ByteBuffer.wrap(data); 28 | checksum = buffer.getInt(); 29 | sequenceNumber = buffer.getInt(); 30 | this.data = Arrays.copyOfRange(buffer.array(), Constants.headerSize, data.length); 31 | if (expectMeta && sequenceNumber == 0 && isValid()) { 32 | fileLength = buffer.getLong(); 33 | fileName = new String(this.data, 12, buffer.getInt()); 34 | } 35 | } 36 | 37 | public Segment(DatagramPacket packet) { 38 | this(packet, false); 39 | } 40 | 41 | public boolean isValid() { 42 | int sum = checksum; 43 | sum += sequenceNumber; 44 | for (byte b : data) { 45 | sum += b; 46 | } 47 | return sum == 0; 48 | } 49 | 50 | public boolean isMetadata() { 51 | return sequenceNumber == 0; 52 | } 53 | 54 | public long getFileLength() { 55 | return fileLength; 56 | } 57 | 58 | public String getFileName() { 59 | return fileName; 60 | } 61 | 62 | public int getDataLength() { 63 | return data.length; 64 | } 65 | 66 | public int getSequenceNumber() { 67 | return sequenceNumber; 68 | } 69 | 70 | public byte[] getData() { 71 | return data; 72 | } 73 | 74 | public byte[] getBytes() { 75 | ByteBuffer buffer = ByteBuffer.allocate(data.length + Constants.headerSize); 76 | buffer.putInt(checksum); 77 | buffer.putInt(sequenceNumber); 78 | buffer.put(data, 0, data.length); 79 | return buffer.array(); 80 | } 81 | 82 | public int length() { 83 | return data.length + Constants.headerSize; 84 | } 85 | } 86 | --------------------------------------------------------------------------------