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 }