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.cancel();
128            }
129    
130            public void connect(Server s) {
131                connect(s.port);
132            }
133    
134            public void connect(int port) {
135                connect(URI.create("conn://localhost:" + port));
136            }
137    
138            public void connect(final URI uri) {
139                queue.execute(new Runnable() {
140                    public void run() {
141                        if (me.equals(uri) || seen.contains(uri))
142                            return;
143    
144                        try {
145                            int port = uri.getPort();
146                            String host = uri.getHost();
147    
148                            trace("open " + uri);
149    
150                            final SocketChannel socketChannel = SocketChannel.open();
151                            socketChannel.configureBlocking(false);
152    
153                            final InetSocketAddress address = new InetSocketAddress(host, port);
154    
155                            socketChannel.connect(address);
156    
157                            final DispatchSource connect_source = createSource(socketChannel, SelectionKey.OP_CONNECT, queue);
158                            connect_source.setEventHandler(new Runnable() {
159                                public void run() {
160                                    connect_source.cancel();
161                                    try {
162                                        socketChannel.finishConnect();
163                                        trace("connected " + uri);
164                                        Session session = new Session(Server.this, socketChannel, address, uri);
165                                        session.start_write_greeting();
166                                    } catch (IOException e) {
167                                        trace("connect to " + uri + " FAILED.");
168                                    }
169                                }
170                            });
171                            connect_source.resume();
172                            seen.add(uri);
173    
174                        } catch (IOException e) {
175                            e.printStackTrace();
176                        }
177                    }
178                });
179            }
180    
181            public void trace(String str) {
182                System.out.println(String.format("%5d       - %s", port, str));
183            }
184    
185        }
186    
187        static class Session {
188    
189            Server server;
190            SocketChannel channel;
191            InetSocketAddress address;
192            URI uri;
193    
194            ByteBuffer read_buffer = ByteBuffer.allocate(1024);
195    
196            DispatchQueue queue;
197            DispatchSource read_source;
198            DispatchSource write_source;
199            ArrayList<URI> seen;
200            ArrayList<URI> listed = new ArrayList<URI>();
201    
202    
203            public Session(Server server, SocketChannel channel, InetSocketAddress address, URI uri) {
204                this.server = server;
205                this.channel = channel;
206                this.address = address;
207                this.uri = uri;
208    
209                this.queue = createQueue(uri.toString());
210                this.read_source = createSource(channel, SelectionKey.OP_READ, queue);
211                this.write_source = createSource(channel, SelectionKey.OP_WRITE, queue);
212                this.seen = new ArrayList<URI>(server.seen);
213    
214            }
215    
216            public Session(Server server, SocketChannel channel, InetSocketAddress address) {
217                this(server, channel, address, URI.create("conn://" + address.getHostName() + ":" + address.getPort()));
218            }
219    
220    
221            public void start_read_greeting() {
222                read_source.setEventHandler(read_greeting());
223                read_source.resume();
224            }
225    
226    
227            public Runnable read_greeting() {
228                return new Runnable() {
229                    public void run() {
230                        try {
231                            String message = read_frame();
232                            if (message != null) {
233                                // stop looking for read events..
234                                read_source.suspend();
235                                URI uri = URI.create(message);
236                                trace("welcome");
237    
238                                // Send them our seen uris..
239                                ArrayList<Object> list = new ArrayList<Object>(seen);
240                                list.remove(server.me);
241                                list.remove(uri);
242                                list.add("end");
243    
244                                start_write_data(new Runnable() {
245                                    public void run() {
246                                        start_read_hearbeat();
247                                    }
248                                }, list.toArray(new Object[list.size()]));
249                            }
250                        } catch (IOException e) {
251                            e.printStackTrace();
252                        }
253                    }
254                };
255            }
256    
257            public void start_write_greeting() throws IOException {
258                trace("hello");
259                start_write_data(new Runnable() {
260                    public void run() {
261                        start_read_server_listings();
262                    }
263                }, server.me);
264            }
265    
266            public void start_read_server_listings() {
267                read_source.setEventHandler(read_server_listings());
268                read_source.resume();
269            }
270    
271    
272            public Runnable read_server_listings() {
273                return new Runnable() {
274                    public void run() {
275                        try {
276                            String message = read_frame();
277                            if (message != null) {
278                                if (!message.equals("end")) {
279                                    URI uri = URI.create(message);
280                                    listed.add(uri);
281                                    server.connect(uri);
282                                } else {
283                                    // Send them our seen uris..
284                                    ArrayList<Object> list = new ArrayList<Object>(seen);
285                                    list.removeAll(listed);
286                                    list.remove(server.me);
287                                    list.add("end");
288                                    start_write_data(new Runnable(){
289                                        public void run() {
290                                            start_write_hearbeat();
291                                        }
292                                    }, list.toArray(new Object[list.size()]));
293                                }
294                            }
295                        } catch (IOException e) {
296                            e.printStackTrace();
297                        }
298                    }
299                };
300            }
301    
302            public void start_read_client_listings() {
303                read_source.setEventHandler(read_clientlistings());
304                read_source.resume();
305            }
306    
307            public Runnable read_clientlistings() {
308                return new Runnable() {
309                    public void run() {
310                        try {
311                            String message = read_frame();
312                            if (message != null) {
313                                if (!message.equals("end")) {
314                                    server.connect(URI.create(message));
315                                } else {
316                                    start_read_hearbeat();
317                                }
318                            }
319                        } catch (IOException e) {
320                            e.printStackTrace();
321                        }
322                    }
323                };
324            }
325    
326            public void start_write_hearbeat() {
327                queue.executeAfter(1, TimeUnit.SECONDS, new Runnable() {
328                    public void run() {
329                        try {
330                            trace("ping");
331                            start_write_data(new Runnable() {
332                                public void run() {
333                                    start_write_hearbeat();
334                                }
335                            }, "ping");
336                        } catch (IOException e) {
337                            e.printStackTrace();
338                        }
339                    }
340                });
341            }
342    
343    
344            public void start_read_hearbeat() {
345                read_source.setEventHandler(read_hearbeat());
346                read_source.resume();
347            }
348    
349            public Runnable read_hearbeat() {
350                return new Runnable() {
351                    public void run() {
352                        try {
353                            String message = read_frame();
354                            if (message != null) {
355                                trace("pong");
356                            }
357                        } catch (IOException e) {
358                            e.printStackTrace();
359                        }
360                    }
361                };
362            }
363    
364            public void start_write_data(Runnable onDone, Object... list) throws IOException {
365                ByteArrayOutputStream baos = new ByteArrayOutputStream();
366                for (Object next : list) {
367                    baos.write(next.toString().getBytes("UTF-8"));
368                    baos.write(0);
369                }
370                ByteBuffer buffer = ByteBuffer.wrap(baos.toByteArray());
371                write_source.setEventHandler(write_data(buffer, onDone));
372                write_source.resume();
373            }
374    
375            public Runnable write_data(final ByteBuffer buffer, final Runnable onDone) {
376                return new Runnable() {
377                    public void run() {
378                        try {
379                            channel.write(buffer);
380                            if (buffer.remaining() == 0) {
381                                write_source.suspend();
382                                onDone.run();
383                            }
384                        } catch (IOException e) {
385                            e.printStackTrace();
386                        }
387                    }
388                };
389            }
390    
391            public String read_frame() throws IOException {
392                if (channel.read(read_buffer) == -1) {
393                    throw new EOFException();
394                }
395                byte[] buf = read_buffer.array();
396                int endPos = eof(buf, 0, read_buffer.position());
397                if (endPos < 0) {
398                    trace(" --- ");
399                    return null;
400                }
401                String rc = new String(buf, 0, endPos);
402                int newPos = read_buffer.position() - endPos;
403                System.arraycopy(buf, endPos + 1, buf, 0, newPos);
404                read_buffer.position(newPos);
405                return rc;
406            }
407    
408            public int eof(byte[] data, int offset, int pos) {
409                int i = offset;
410                while (i < pos) {
411                    if (data[i] == 0) {
412                        return i;
413                    }
414                    i++;
415                }
416                return -1;
417            }
418    
419            public void trace(String str) {
420                System.out.println(String.format("%5d %5d - %s", server.port, uri.getPort(), str));
421            }
422    
423    
424        }
425    
426    }