001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.fusesource.hawtdispatch.example.discovery;
018    
019    import org.fusesource.hawtdispatch.*;
020    
021    import static org.fusesource.hawtdispatch.Dispatch.*;
022    
023    import java.io.ByteArrayOutputStream;
024    import java.io.EOFException;
025    import java.io.IOException;
026    import java.net.InetSocketAddress;
027    import java.net.URI;
028    import java.nio.ByteBuffer;
029    import java.nio.channels.*;
030    import java.util.ArrayList;
031    import java.util.concurrent.TimeUnit;
032    
033    /**
034     * An example of a networks of servers which advertise connection information to each other.
035     */
036    public class EchoNetJava {
037        public static void main(String[] args) throws Exception {
038            run();
039        }
040    
041        public static void run() throws Exception {
042            Server a = new Server(4444).start();
043            Server b = new Server(5555).start();
044            Server c = new Server(6666).start();
045    
046            Thread.sleep(200);
047    
048            a.connect(3333);
049            a.connect(b);
050            b.connect(c);
051            System.in.read();
052        }
053    
054        static class Server {
055            final int port;
056            final URI me;
057            final ServerSocketChannel serverChannel;
058            final ArrayList<URI> seen = new ArrayList<URI>();
059            final DispatchQueue queue;
060            final DispatchSource accept_source;
061    
062    
063            public Server(int port) throws Exception {
064                this.port = port;
065                this.me = URI.create("conn://localhost:" + port);
066                this.serverChannel = ServerSocketChannel.open();
067                serverChannel.socket().bind(new InetSocketAddress(port));
068                serverChannel.configureBlocking(false);
069                queue = createQueue(me.toString());
070                accept_source = createSource(serverChannel, SelectionKey.OP_ACCEPT, queue);
071    
072                accept_source.setEventHandler(new Runnable() {
073                    public void run() {
074                        // we are a server
075    
076                        // when you are a server, we must first listen for the
077                        // address of the client before sending data.
078    
079                        // once they send us their address, we will send our
080                        // full list of known addresses, followed by our own
081                        // address to signal that we are done.
082    
083                        // Afterward we will only pulls our heartbeat
084                        SocketChannel client = null;
085                        try {
086                            client = serverChannel.accept();
087    
088                            InetSocketAddress address = (InetSocketAddress) client.socket().getRemoteSocketAddress();
089                            trace("accept " + address.getPort());
090                            client.configureBlocking(false);
091    
092                            // Server sessions start by reading the client's greeting
093                            Session session = new Session(Server.this, client, address);
094                            session.start_read_greeting();
095    
096                        } catch (Exception e) {
097                            try {
098                                client.close();
099                            } catch (IOException e1) {
100                            }
101                        }
102    
103                    }
104                });
105    
106                accept_source.setCancelHandler(new Runnable() {
107                    public void run() {
108                        try {
109                            serverChannel.close();
110                        } catch (Throwable e) {
111                        }
112                    }
113                });
114                trace("Listening");
115            }
116    
117            public Server start() {
118                accept_source.resume();
119                return this;
120            }
121    
122            public void stop() {
123                accept_source.suspend();
124            }
125    
126            public void close() {
127                accept_source.release();
128                queue.release();
129            }
130    
131            public void connect(Server s) {
132                connect(s.port);
133            }
134    
135            public void connect(int port) {
136                connect(URI.create("conn://localhost:" + port));
137            }
138    
139            public void connect(final URI uri) {
140                queue.dispatchAsync(new Runnable() {
141                    public void run() {
142                        if (me.equals(uri) || seen.contains(uri))
143                            return;
144    
145                        try {
146                            int port = uri.getPort();
147                            String host = uri.getHost();
148    
149                            trace("open " + uri);
150    
151                            final SocketChannel socketChannel = SocketChannel.open();
152                            socketChannel.configureBlocking(false);
153    
154                            final InetSocketAddress address = new InetSocketAddress(host, port);
155    
156                            socketChannel.connect(address);
157    
158                            final DispatchSource connect_source = createSource(socketChannel, SelectionKey.OP_CONNECT, queue);
159                            connect_source.setEventHandler(new Runnable() {
160                                public void run() {
161                                    connect_source.release();
162                                    try {
163                                        socketChannel.finishConnect();
164                                        trace("connected " + uri);
165                                        Session session = new Session(Server.this, socketChannel, address, uri);
166                                        session.start_write_greeting();
167                                    } catch (IOException e) {
168                                        trace("connect to " + uri + " FAILED.");
169                                    }
170                                }
171                            });
172                            connect_source.resume();
173                            seen.add(uri);
174    
175                        } catch (IOException e) {
176                            e.printStackTrace();
177                        }
178                    }
179                });
180            }
181    
182            public void trace(String str) {
183                System.out.println(String.format("%5d       - %s", port, str));
184            }
185    
186        }
187    
188        static class Session {
189    
190            Server server;
191            SocketChannel channel;
192            InetSocketAddress address;
193            URI uri;
194    
195            ByteBuffer read_buffer = ByteBuffer.allocate(1024);
196    
197            DispatchQueue queue;
198            DispatchSource read_source;
199            DispatchSource write_source;
200            ArrayList<URI> seen;
201            ArrayList<URI> listed = new ArrayList<URI>();
202    
203    
204            public Session(Server server, SocketChannel channel, InetSocketAddress address, URI uri) {
205                this.server = server;
206                this.channel = channel;
207                this.address = address;
208                this.uri = uri;
209    
210                this.queue = createQueue(uri.toString());
211                this.read_source = createSource(channel, SelectionKey.OP_READ, queue);
212                this.write_source = createSource(channel, SelectionKey.OP_WRITE, queue);
213                this.seen = new ArrayList<URI>(server.seen);
214    
215            }
216    
217            public Session(Server server, SocketChannel channel, InetSocketAddress address) {
218                this(server, channel, address, URI.create("conn://" + address.getHostName() + ":" + address.getPort()));
219            }
220    
221    
222            public void start_read_greeting() {
223                read_source.setEventHandler(read_greeting());
224                read_source.resume();
225            }
226    
227    
228            public Runnable read_greeting() {
229                return new Runnable() {
230                    public void run() {
231                        try {
232                            String message = read_frame();
233                            if (message != null) {
234                                // stop looking for read events..
235                                read_source.suspend();
236                                URI uri = URI.create(message);
237                                trace("welcome");
238    
239                                // Send them our seen uris..
240                                ArrayList<Object> list = new ArrayList<Object>(seen);
241                                list.remove(server.me);
242                                list.remove(uri);
243                                list.add("end");
244    
245                                start_write_data(new Runnable() {
246                                    public void run() {
247                                        start_read_hearbeat();
248                                    }
249                                }, list.toArray(new Object[list.size()]));
250                            }
251                        } catch (IOException e) {
252                            e.printStackTrace();
253                        }
254                    }
255                };
256            }
257    
258            public void start_write_greeting() throws IOException {
259                trace("hello");
260                start_write_data(new Runnable() {
261                    public void run() {
262                        start_read_server_listings();
263                    }
264                }, server.me);
265            }
266    
267            public void start_read_server_listings() {
268                read_source.setEventHandler(read_server_listings());
269                read_source.resume();
270            }
271    
272    
273            public Runnable read_server_listings() {
274                return new Runnable() {
275                    public void run() {
276                        try {
277                            String message = read_frame();
278                            if (message != null) {
279                                if (!message.equals("end")) {
280                                    URI uri = URI.create(message);
281                                    listed.add(uri);
282                                    server.connect(uri);
283                                } else {
284                                    // Send them our seen uris..
285                                    ArrayList<Object> list = new ArrayList<Object>(seen);
286                                    list.removeAll(listed);
287                                    list.remove(server.me);
288                                    list.add("end");
289                                    start_write_data(new Runnable(){
290                                        public void run() {
291                                            start_write_hearbeat();
292                                        }
293                                    }, list.toArray(new Object[list.size()]));
294                                }
295                            }
296                        } catch (IOException e) {
297                            e.printStackTrace();
298                        }
299                    }
300                };
301            }
302    
303            public void start_read_client_listings() {
304                read_source.setEventHandler(read_clientlistings());
305                read_source.resume();
306            }
307    
308            public Runnable read_clientlistings() {
309                return new Runnable() {
310                    public void run() {
311                        try {
312                            String message = read_frame();
313                            if (message != null) {
314                                if (!message.equals("end")) {
315                                    server.connect(URI.create(message));
316                                } else {
317                                    start_read_hearbeat();
318                                }
319                            }
320                        } catch (IOException e) {
321                            e.printStackTrace();
322                        }
323                    }
324                };
325            }
326    
327            public void start_write_hearbeat() {
328                queue.dispatchAfter(1, TimeUnit.SECONDS, new Runnable() {
329                    public void run() {
330                        try {
331                            trace("ping");
332                            start_write_data(new Runnable() {
333                                public void run() {
334                                    start_write_hearbeat();
335                                }
336                            }, "ping");
337                        } catch (IOException e) {
338                            e.printStackTrace();
339                        }
340                    }
341                });
342            }
343    
344    
345            public void start_read_hearbeat() {
346                read_source.setEventHandler(read_hearbeat());
347                read_source.resume();
348            }
349    
350            public Runnable read_hearbeat() {
351                return new Runnable() {
352                    public void run() {
353                        try {
354                            String message = read_frame();
355                            if (message != null) {
356                                trace("pong");
357                            }
358                        } catch (IOException e) {
359                            e.printStackTrace();
360                        }
361                    }
362                };
363            }
364    
365            public void start_write_data(Runnable onDone, Object... list) throws IOException {
366                ByteArrayOutputStream baos = new ByteArrayOutputStream();
367                for (Object next : list) {
368                    baos.write(next.toString().getBytes("UTF-8"));
369                    baos.write(0);
370                }
371                ByteBuffer buffer = ByteBuffer.wrap(baos.toByteArray());
372                write_source.setEventHandler(write_data(buffer, onDone));
373                write_source.resume();
374            }
375    
376            public Runnable write_data(final ByteBuffer buffer, final Runnable onDone) {
377                return new Runnable() {
378                    public void run() {
379                        try {
380                            channel.write(buffer);
381                            if (buffer.remaining() == 0) {
382                                write_source.suspend();
383                                onDone.run();
384                            }
385                        } catch (IOException e) {
386                            e.printStackTrace();
387                        }
388                    }
389                };
390            }
391    
392            public String read_frame() throws IOException {
393                if (channel.read(read_buffer) == -1) {
394                    throw new EOFException();
395                }
396                byte[] buf = read_buffer.array();
397                int endPos = eof(buf, 0, read_buffer.position());
398                if (endPos < 0) {
399                    trace(" --- ");
400                    return null;
401                }
402                String rc = new String(buf, 0, endPos);
403                int newPos = read_buffer.position() - endPos;
404                System.arraycopy(buf, endPos + 1, buf, 0, newPos);
405                read_buffer.position(newPos);
406                return rc;
407            }
408    
409            public int eof(byte[] data, int offset, int pos) {
410                int i = offset;
411                while (i < pos) {
412                    if (data[i] == 0) {
413                        return i;
414                    }
415                    i++;
416                }
417                return -1;
418            }
419    
420            public void trace(String str) {
421                System.out.println(String.format("%5d %5d - %s", server.port, uri.getPort(), str));
422            }
423    
424    
425        }
426    
427    }