Distributed Hash Table

 

  Chord Algorithm is used to search the Distributed Hash Table (DHT) in a Peer-to-Peer Network, where a peer only remembers the addresses of a few companions but can get access to any peer who is responsible for a given key. This demo program is largely based on such thinking although there may be some modifications and simplifications.

  Moreover, in this program I used Java UDP sockets to simulate a simple reliable data transfer protocol, which can deal with bit error, packet error and packet loss. A message is encapsulated with a SEQ number, an ACK number and a CRC checksum. To simplify the application layer programming, I provide a Client class and a Server class with some convenient APIs. A Server should always listen and answer alternatively, and can be informed or inquired by calling a Client method.

 

 

  Makefile:

all: ./bin/Chord.class ./bin/Peer.class

./bin/Chord.class: ./src/Chord.java ./src/Peer.java
    javac -classpath ./bin/ -d ./bin/ ./src/Chord.java

./bin/Peer.class: ./src/Peer.java
    javac -d ./bin/ ./src/Peer.java

clean:
    rm ./bin/*.class

 

 

1. Chord.java

 1 import java.util.concurrent.*;
 2 import java.util.*;
 3 import java.io.*;
 4 
 5 
 6 public class Chord extends Thread {
 7     private static int num;
 8     private static Process[] p;
 9     private static Chord[] t;
10     private static int[] port;
11     private BufferedReader in;
12     private PrintWriter out;
13     
14     public Chord(Process proc) {
15         in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
16         out = new PrintWriter(new OutputStreamWriter(proc.getOutputStream()));
17         start();
18     }
19     public void run() {
20         try {
21             out.println(num);
22             for (int i=0;i<num;i++) {
23                 out.println(port[i]);
24             }
25             out.close();
26             String str = null;
27             while (true) {
28                 str = in.readLine();
29                 if (str.charAt(0)!='#') {
30                     System.out.println(str);
31                 } else {
32                     break;
33                 }
34             }
35             in.close();
36         } catch (Exception e) {
37             e.printStackTrace();
38         }
39     }
40     public static void main(String[] args) {
41         num = Integer.parseInt(args[0]);
42         p = new Process[num];
43         t = new Chord[num];
44         port = new int[num];
45         Random rand = new Random();
46         for (int i=0;i<num;i++) {
47             port[i] = 1024+rand.nextInt(8192);
48         }
49         System.out.println("\nWait Please ~\n");
50         try {
51             for (int i=0;i<num;i++) {
52                 /* Thread t[i] is resp for the standard IO of Process p[i] */
53                 p[i] = Runtime.getRuntime().exec("java -classpath ./bin/ Peer "+port[i]);
54                 t[i] = new Chord(p[i]);
55                 TimeUnit.MILLISECONDS.sleep(100);
56             }
57             for (int i=0;i<num;i++) {
58                 p[i].waitFor();
59                 t[i].join();
60             }
61         } catch (Exception e) {
62             e.printStackTrace();
63         }
64         System.out.println();
65     }
66 }

 

 

2. Peer.java

  1 import java.util.concurrent.atomic.*;
  2 import java.util.concurrent.*;
  3 import java.util.*;
  4 import java.text.*;
  5 import java.net.*;
  6 import java.io.*;
  7 
  8 
  9 abstract class RDT {
 10     /** RDT is a class using UDP sockets to simulate
 11      *        reliable data transfer that implements packet
 12      *        loss/error detection and retransmission.
 13      *    An RDT message = SEQ + ACK + content + CRC checksum
 14      *        if (SEQ==0)
 15      *            no reply is expected, a client "ACK" message
 16      *        else
 17      *            a reply with the identical ACK is expected
 18      */
 19     
 20     private static final int GEN = 0x04C11DB7;
 21     private static final int SIZE = 256;
 22     protected static final int TIME_LIMIT = 10;
 23     protected static Random rand;
 24     protected InetAddress addr;
 25     protected int targetPort;
 26     protected DatagramSocket socket;
 27     private DatagramPacket packet;
 28     private byte[] buffer;
 29     private byte ack = 0;
 30         
 31     static {
 32         rand = new Random();
 33     }
 34     protected String rcvd() {
 35         /* Receive a message with desired ACK # */
 36         try {
 37             do {
 38                 buffer = new byte[SIZE];
 39                 packet = new DatagramPacket(buffer,SIZE);
 40                 socket.receive(packet);
 41             } while (checksum()!=0||buffer[1]!=ack);
 42             ack = buffer[0];    // next ACK # to be written
 43             if (ack!=0) {
 44                 addr = packet.getAddress();
 45                 targetPort = packet.getPort();
 46             }
 47             return new String(buffer,"ASCII").substring(2,SIZE-4);
 48         } catch (Exception e) {
 49             // Timeout Exception: the packet is lost!
 50             return null;
 51         }
 52     }
 53     protected void send(byte seq,String str) {
 54         /* Send a message with 'best effort' */
 55         buffer = new byte[SIZE];
 56         buffer[0] = seq;
 57         buffer[1] = ack;
 58         try {    // write the data content
 59             byte[] tmp = str.getBytes("ASCII");
 60             System.arraycopy(tmp,0,buffer,2,tmp.length);
 61         } catch (Exception e) {
 62             e.printStackTrace();
 63         }
 64         // write the checksum field
 65         int cks = checksum();
 66         buffer[SIZE-4] = (byte)(cks&0xFF);
 67         cks >>= 8;
 68         buffer[SIZE-3] = (byte)(cks&0xFF);
 69         cks >>= 8;
 70         buffer[SIZE-2] = (byte)(cks&0xFF);
 71         cks >>= 8;
 72         buffer[SIZE-1] = (byte)(cks&0xFF);
 73         // encapsulate and send the packet
 74         packet = new DatagramPacket(buffer,SIZE,addr,targetPort);
 75         try {
 76             socket.send(packet);
 77         } catch (Exception e) {
 78             System.out.println("Cannot send msg: "+str);
 79         }
 80         // next ACK # to be read
 81         ack = buffer[0];
 82     }
 83     private int checksum() {
 84         /* Calculate the CRC checksum */
 85         int rem = 0, N = (SIZE<<3);
 86         for (int pos=0;pos<N;pos++) {
 87             byte tmp = (byte)(buffer[pos>>3]>>(pos&7));
 88             if (rem>=0) {
 89                 rem = (rem<<1)|(tmp&1);
 90             } else {
 91                 rem = (rem<<1)|(tmp&1);
 92                 rem ^= GEN;
 93             }
 94         }
 95         return revBits(rem);
 96     }
 97     private int revBits(int val) {
 98         /* Reverse the bits of an integer */
 99         int tmp = 0;
100         for (int i=0;i<16;i++) {
101             tmp |= (((1<<i)&val)<<(31-i-i));
102             tmp |= (((1<<(31-i))&val)>>>(31-i-i));
103         }
104         return tmp;
105     }
106     protected static boolean isACK(String str) throws Exception {
107         /* Test whether a string is equal to "ACK" */
108         if (str==null) {
109             return false;
110         } else {
111             byte[] b = str.getBytes("ASCII");
112             return (b.length>=3 && b[0]=='A'
113                 && b[1]=='C' && b[2]=='K');
114         }
115     }
116 }
117 
118 
119 class Client extends RDT {
120     /**    An RDT client should always send a message first,
121      *        then receives a reply, and finally send an "ACK"
122      *         message to terminate the conversation.
123      *     An "ACK" message from the client indicates that
124      *        it has received the reply and left.
125      */
126     
127     private Client(int port) {
128         try {
129             addr = InetAddress.getByName("localhost");
130             targetPort = port;
131             socket = new DatagramSocket();
132             socket.setSoTimeout(TIME_LIMIT);
133         } catch(Exception e) {
134             e.printStackTrace();
135         }
136     }
137     public static String inquire(int port,String str) {
138         /* Send a inquiry and return a pending reply */
139         Client c = new Client(port);
140         String res = null;
141         while (res==null) {
142             c.send((byte)(1+rand.nextInt(255)),str);
143             res = c.rcvd();
144         }
145         c.send((byte)0,"ACK");
146         c.socket.close();
147         return res;
148     }
149     public static void inform(int port,String str) {
150         /* Send a piece of info and get an "ACK" */
151         Client c = new Client(port);
152         String res = null;
153         try {
154             while (!isACK(res)) {
155                 c.send((byte)(1+rand.nextInt(255)),str);
156                 res = c.rcvd();
157             }
158         } catch (Exception e) {
159             e.printStackTrace();
160         }
161         c.send((byte)0,"ACK");
162         c.socket.close();
163     }
164 }
165 
166 
167 class Server extends RDT {
168     /**    An RDT server should always listen to a request 
169      *        first, then respond with a reply, and finally
170      *        get an "ACK" message.
171      *    An "ACK" message from the server indicates that
172      *        it has been informed of something.
173      */
174     
175     public Server(int port) {
176         try {
177             socket = new DatagramSocket(port);
178             socket.setSoTimeout(TIME_LIMIT);
179         } catch (Exception e) {
180             e.printStackTrace();
181         }
182     }
183     public String listen() {
184         String req = null;
185         while (req==null) {
186             req = rcvd();
187         }
188         return req;
189     }
190     public void answer(String str) {
191         String res = null;
192         try {
193             while (!isACK(res)) {
194                 send((byte)(1+rand.nextInt(255)),str);
195                 res = rcvd();
196             }
197         } catch (Exception e) {
198             e.printStackTrace();
199         }
200     }
201 }
202 
203 
204 public class Peer extends Thread {
205     /**    A node on a Peer-to-Peer Network
206      *    For consecutive peers with indexes i and j:
207      *         peer[i] is resp for key k iff i <= k < j;
208      *     finger[k] is the port # of the on-line peer with
209      *         the minimum index not less than index+2^{k}.
210      */
211     
212     private static final int LEN = 16;
213     private static final int NUM = (1<<LEN);
214     private static DecimalFormat df;
215     private static int port, index;
216     private static AtomicInteger[] finger;
217     private Server sv;
218     
219     private Peer() {
220         sv = new Server(port);
221         start();
222     }
223     public void run() {
224         while (true) {
225             String msg = sv.listen();
226             if (msg.charAt(0)=='?') {
227                 msg = msg.substring(1,6);
228                 int next = search(Integer.parseInt(msg));
229                 if (next==port) {
230                     sv.answer("!");
231                 } else {
232                     sv.answer("@"+next);
233                 }
234             } else if (msg.charAt(0)=='#') {
235                 sv.answer("ACK");
236                 break;
237             }
238         }
239         System.out.println("#");
240     }
241     private static int calDelt(int val) {
242         int delt = hashIdx(val)-index;
243         return delt+((delt<=0)? NUM:0);
244     }
245     private static boolean within(int a,int x,int b) {
246         a -= index;
247         a += (a<0)? NUM:0;
248         x -= index;
249         x += (x<0)? NUM:0;
250         b -= index;
251         b += (b<0)? NUM:0;
252         return a<=x && x<b;
253     }
254     private static boolean insfinger(int val) {
255         /* Insert a port number to the finger table */
256         boolean flag = false;
257         if (val==port) {
258             return flag;
259         }
260         int delt = calDelt(val);
261         for (int i=0;(1<<i)<=delt;i++) {
262             if (delt<calDelt(finger[i].get())) {
263                 finger[i].set(val);
264                 flag = true;
265             }
266         }
267         return flag;
268     }
269     private static int search(int key) {
270         /* Chord Algorithm Implementation */
271         if (within(index,key,hashIdx(finger[0].get()))) {
272             return port;
273         }
274         for (int i=0;i<LEN-1;i++) {
275             int a = hashIdx(finger[i].get());
276             int b = hashIdx(finger[i+1].get());
277             if (b==index||within(a,key,b)) {
278                 return finger[i].get();
279             }
280         }
281         return finger[LEN-1].get();
282     }
283     private static int locate(int key) {
284         /* Locate the peer who is resp for a given key */
285         int next = search(key);
286         if (next==port) {
287             return port;
288         }
289         String s = null;
290         while (true) {
291             s = Client.inquire(next,"?"+df.format(key));
292             if (s.charAt(0)=='@') {
293                 s = s.substring(1,5);
294                 next = Integer.parseInt(s);
295             } else {
296                 break;
297             }
298         }
299         return next;
300     }
301     private static int hashIdx(int key) {
302         /* Hash function using multiplication */
303         double tmp = 0.61803*key;
304         tmp -= Math.floor(tmp);
305         return (int)Math.floor(NUM*tmp);
306     }
307     public static void main(String[] args) {
308         df = new DecimalFormat("00000");
309         try {
310             port = Integer.parseInt(args[0]);
311             index = hashIdx(port);
312             finger = new AtomicInteger[LEN];
313             for (int i=0;i<LEN;i++) {
314                 finger[i] = new AtomicInteger(port);
315             }
316             // acquaint all the on-line peers
317             Scanner in = new Scanner(System.in);
318             int num = in.nextInt();
319             for (int i=0;i<num;i++) {
320                 insfinger(in.nextInt());
321             }
322             Peer server = new Peer();
323             // for all peers to set up the finger table
324             TimeUnit.MILLISECONDS.sleep(num*100);
325             Random rand = new Random();
326             for (int i=0;i<3;i++) {
327                 // Each peer has three searching tasks.
328                 int key = rand.nextInt(NUM);
329                 System.out.println("Port "+port+": port "+locate(key)
330                         +" is resp for key "+df.format(key));
331             }
332             // for all peers to finish the searching task
333             TimeUnit.MILLISECONDS.sleep(num*100);
334             Client.inform(port,"#");
335             server.join();
336         } catch (Exception e) {
337             System.out.println("Main Error: "+e);
338         }
339     }
340 }

 

 

References:

  1. Kurose, James F., Keith W. Ross. Computer Networking: a top-down approach[M]. 北京:高等教育出版社, 2009-08

  2. Tanenbaum, Andrew S., David J. Wetherall. Computer Networks 5th edition[M]. 北京:清华大学出版社, 2011

 

posted on 2015-06-06 19:27  DevinZ  阅读(303)  评论(0编辑  收藏  举报

导航