/** * Copyright (C) 2012 FuseSource, Inc. * http://fusesource.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.fusesource.hawtdispatch.example.discovery; import org.fusesource.hawtdispatch.*; import static org.fusesource.hawtdispatch.Dispatch.*; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.ArrayList; import java.util.concurrent.TimeUnit; /** * An example of a networks of servers which advertise connection information to each other. */ public class EchoNetJava { public static void main(String[] args) throws Exception { run(); } public static void run() throws Exception { Server a = new Server(4444).start(); Server b = new Server(5555).start(); Server c = new Server(6666).start(); Thread.sleep(200); a.connect(3333); a.connect(b); b.connect(c); System.in.read(); } static class Server { final int port; final URI me; final ServerSocketChannel serverChannel; final ArrayList<URI> seen = new ArrayList<URI>(); final DispatchQueue queue; final DispatchSource accept_source; public Server(int port) throws Exception { this.port = port; this.me = URI.create("conn://localhost:" + port); this.serverChannel = ServerSocketChannel.open(); serverChannel.socket().bind(new InetSocketAddress(port)); serverChannel.configureBlocking(false); queue = createQueue(me.toString()); accept_source = createSource(serverChannel, SelectionKey.OP_ACCEPT, queue); accept_source.setEventHandler(new Task() { public void run() { // we are a server // when you are a server, we must first listen for the // address of the client before sending data. // once they send us their address, we will send our // full list of known addresses, followed by our own // address to signal that we are done. // Afterward we will only pulls our heartbeat SocketChannel client = null; try { client = serverChannel.accept(); InetSocketAddress address = (InetSocketAddress) client.socket().getRemoteSocketAddress(); trace("accept " + address.getPort()); client.configureBlocking(false); // Server sessions start by reading the client's greeting Session session = new Session(Server.this, client, address); session.start_read_greeting(); } catch (Exception e) { try { client.close(); } catch (IOException e1) { } } } }); accept_source.setCancelHandler(new Task() { public void run() { try { serverChannel.close(); } catch (Throwable e) { } } }); trace("Listening"); } public Server start() { accept_source.resume(); return this; } public void stop() { accept_source.suspend(); } public void close() { accept_source.cancel(); } public void connect(Server s) { connect(s.port); } public void connect(int port) { connect(URI.create("conn://localhost:" + port)); } public void connect(final URI uri) { queue.execute(new Task() { public void run() { if (me.equals(uri) || seen.contains(uri)) return; try { int port = uri.getPort(); String host = uri.getHost(); trace("open " + uri); final SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); final InetSocketAddress address = new InetSocketAddress(host, port); socketChannel.connect(address); final DispatchSource connect_source = createSource(socketChannel, SelectionKey.OP_CONNECT, queue); connect_source.setEventHandler(new Task() { public void run() { connect_source.cancel(); try { socketChannel.finishConnect(); trace("connected " + uri); Session session = new Session(Server.this, socketChannel, address, uri); session.start_write_greeting(); } catch (IOException e) { trace("connect to " + uri + " FAILED."); } } }); connect_source.resume(); seen.add(uri); } catch (IOException e) { e.printStackTrace(); } } }); } public void trace(String str) { System.out.println(String.format("%5d - %s", port, str)); } } static class Session { Server server; SocketChannel channel; InetSocketAddress address; URI uri; ByteBuffer read_buffer = ByteBuffer.allocate(1024); DispatchQueue queue; DispatchSource read_source; DispatchSource write_source; ArrayList<URI> seen; ArrayList<URI> listed = new ArrayList<URI>(); public Session(Server server, SocketChannel channel, InetSocketAddress address, URI uri) { this.server = server; this.channel = channel; this.address = address; this.uri = uri; this.queue = createQueue(uri.toString()); this.read_source = createSource(channel, SelectionKey.OP_READ, queue); this.write_source = createSource(channel, SelectionKey.OP_WRITE, queue); this.seen = new ArrayList<URI>(server.seen); } public Session(Server server, SocketChannel channel, InetSocketAddress address) { this(server, channel, address, URI.create("conn://" + address.getHostName() + ":" + address.getPort())); } public void start_read_greeting() { read_source.setEventHandler(read_greeting()); read_source.resume(); } public Task read_greeting() { return new Task() { public void run() { try { String message = read_frame(); if (message != null) { // stop looking for read events.. read_source.suspend(); URI uri = URI.create(message); trace("welcome"); // Send them our seen uris.. ArrayList<Object> list = new ArrayList<Object>(seen); list.remove(server.me); list.remove(uri); list.add("end"); start_write_data(new Task() { public void run() { start_read_hearbeat(); } }, list.toArray(new Object[list.size()])); } } catch (IOException e) { e.printStackTrace(); } } }; } public void start_write_greeting() throws IOException { trace("hello"); start_write_data(new Task() { public void run() { start_read_server_listings(); } }, server.me); } public void start_read_server_listings() { read_source.setEventHandler(read_server_listings()); read_source.resume(); } public Task read_server_listings() { return new Task() { public void run() { try { String message = read_frame(); if (message != null) { if (!message.equals("end")) { URI uri = URI.create(message); listed.add(uri); server.connect(uri); } else { // Send them our seen uris.. ArrayList<Object> list = new ArrayList<Object>(seen); list.removeAll(listed); list.remove(server.me); list.add("end"); start_write_data(new Task(){ public void run() { start_write_hearbeat(); } }, list.toArray(new Object[list.size()])); } } } catch (IOException e) { e.printStackTrace(); } } }; } public void start_read_client_listings() { read_source.setEventHandler(read_clientlistings()); read_source.resume(); } public Task read_clientlistings() { return new Task() { public void run() { try { String message = read_frame(); if (message != null) { if (!message.equals("end")) { server.connect(URI.create(message)); } else { start_read_hearbeat(); } } } catch (IOException e) { e.printStackTrace(); } } }; } public void start_write_hearbeat() { queue.executeAfter(1, TimeUnit.SECONDS, new Task() { public void run() { try { trace("ping"); start_write_data(new Task() { public void run() { start_write_hearbeat(); } }, "ping"); } catch (IOException e) { e.printStackTrace(); } } }); } public void start_read_hearbeat() { read_source.setEventHandler(read_hearbeat()); read_source.resume(); } public Task read_hearbeat() { return new Task() { public void run() { try { String message = read_frame(); if (message != null) { trace("pong"); } } catch (IOException e) { e.printStackTrace(); } } }; } public void start_write_data(Task onDone, Object... list) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (Object next : list) { baos.write(next.toString().getBytes("UTF-8")); baos.write(0); } ByteBuffer buffer = ByteBuffer.wrap(baos.toByteArray()); write_source.setEventHandler(write_data(buffer, onDone)); write_source.resume(); } public Task write_data(final ByteBuffer buffer, final Task onDone) { return new Task() { public void run() { try { channel.write(buffer); if (buffer.remaining() == 0) { write_source.suspend(); onDone.run(); } } catch (IOException e) { e.printStackTrace(); } } }; } public String read_frame() throws IOException { if (channel.read(read_buffer) == -1) { throw new EOFException(); } byte[] buf = read_buffer.array(); int endPos = eof(buf, 0, read_buffer.position()); if (endPos < 0) { trace(" --- "); return null; } String rc = new String(buf, 0, endPos); int newPos = read_buffer.position() - endPos; System.arraycopy(buf, endPos + 1, buf, 0, newPos); read_buffer.position(newPos); return rc; } public int eof(byte[] data, int offset, int pos) { int i = offset; while (i < pos) { if (data[i] == 0) { return i; } i++; } return -1; } public void trace(String str) { System.out.println(String.format("%5d %5d - %s", server.port, uri.getPort(), str)); } } }