Skip to content

Commit

Permalink
refactor all the code
Browse files Browse the repository at this point in the history
  • Loading branch information
Max DeLiso committed Aug 3, 2014
1 parent 4044d60 commit f5924c2
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 501 deletions.
94 changes: 35 additions & 59 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,35 @@
## Teflon ##

Teflon is a simple peer to peer chat application implemented in Java.
It was written to demonstrate the ugliness of the Java programming language.
The eventual plan is to port this code to a series of other languages.


* * *

### Howto ###

If you want to build and run this code, but don't know how, then don't despair: it's really easy.
Install [eclipse](http://eclipse.org/), and [egit](http://eclipse.org/egit/).
Then, run eclipse, and select File -> Import -> Git -> From URI, and then enter the public URI for this project: git://github.com/maxdeliso/teflon.git
That's it!

Because the repository includes a .project file and some other things, it should automatically build (unless I've messed it up).

### Bugs ###

If you do a build and you find that there is an error in the code which needs to be fixed, are unwilling or unable to fix it yourself, but still care enough to take some action, submit it in the form of an issue to the following URL: https://github.com/maxdeliso/teflon/issues.
If you fix a bug that you found in your fork of this repository, please submit a merge request.

### Binaries ###

The compiled releases are available here: https://github.com/maxdeliso/teflon/releases

* * *

### Concurrency Model ###

Teflon has a pretty simple concurrency model, and essentially maintains three threads.
If you run it in a debugger and check, there are actually a few more but they are managed by the runtime.
The main thread, which begins executing at the entry point, creates two threads and then waits for both of them to finish.
The first thread is called the local handler thread, which uses Swing to interact with the user.
The second thread is called the remote handler thread, which uses a UDP socket to interact with the network.
Both handler threads maintain their own message queue, and implement postMessage(...).
postMessage(...) just acquires a lock for its parent class's message queue, which is really a linked list, and the appends the passed message to it.
In the main loop of each handler thread, the execution flow looks something like this:

while alive:

1. is there any new data?
* if yes, pass it to the opposite handler with postMessage(...)
+ is there anything new in the message queue?
* if yes, send it however I know how

### Future Plans ###

I thought it might be nice to do some of these things eventually.
We'll see if any of them happen.
They are sorted by decreasing interestingness.

1. port it to Scala, because Scala doesn't suck (i.e. has a much more intuitive concurrency model and type system)
+ add support for some kind of encryption. I was thinking to do some kind of challenge based key agreement (SRP?) and then use a symmetric cipher (AES?)
+ maintain a list of peers, and make the client smart enough to intelligently propagate (address, name) tuples
+ find a way around NAT, probably using some ugly mix of uPNP and hole punching
+ add a public key to the (address, name) tuple and bootstrap with a pgp keyserver, leveraging existing PKI and creating (another) secure p2p chat network, as well an indexed DHT
+ manage and configure persistent user preferences
## Teflon ##

Teflon is a simple peer to peer chat application implemented in Java.
It was written to demonstrate the ugliness of the Java programming language.
The eventual plan is to port this code to a series of other languages.


* * *

### Howto ###

If you want to build and run this code, but don't know how, then don't despair: it's really easy.
Install [eclipse](http://eclipse.org/), and [egit](http://eclipse.org/egit/).
Then, run eclipse, and select File -> Import -> Git -> From URI, and then enter the public URI for this project: git://github.com/maxdeliso/teflon.git
That's it!

Because the repository includes a .project file and some other things, it should automatically build (unless I've messed it up).

### Bugs ###

If you do a build and you find that there is an error in the code which needs to be fixed, are unwilling or unable to fix it yourself, but still care enough to take some action, submit it in the form of an issue to the following URL: https://github.com/maxdeliso/teflon/issues.
If you fix a bug that you found in your fork of this repository, please submit a merge request.

### Binaries ###

The compiled releases are available here: https://github.com/maxdeliso/teflon/releases

* * *

### Concurrency Model ###

There are two linked blocking queues, one for incoming messages and one for outgoing messages.
The UI synchronizer polls the incoming message queue and dispatches events to AWT when a new message is seen.
The UDP thread alternates between reading with a short timeout and putting the received messages into the input queue, and polling the outgoing queue for new messages and sending them over UDP.
The Swing UI just posts messages to the output queue when enter is released.
12 changes: 0 additions & 12 deletions Teflon/src/com/megafrock/teflon/CommDestiny.java

This file was deleted.

18 changes: 0 additions & 18 deletions Teflon/src/com/megafrock/teflon/Message.java

This file was deleted.

147 changes: 21 additions & 126 deletions Teflon/src/com/megafrock/teflon/Teflon.java
Original file line number Diff line number Diff line change
@@ -1,151 +1,46 @@
/*
* author: Max DeLiso <[email protected]>
* purpose: graphical UDP chat program
*/

package com.megafrock.teflon;

import java.nio.charset.Charset;
import java.util.concurrent.LinkedBlockingQueue;

import com.megafrock.teflon.handler.LocalHandler;
import com.megafrock.teflon.handler.RemoteHandler;
import com.megafrock.teflon.data.TeflonMessage;
import com.megafrock.teflon.frames.TeflonFrame;
import com.megafrock.teflon.runnables.SwingTransferRunnable;
import com.megafrock.teflon.runnables.UDPTransferRunnable;

public class Teflon {
/*
* These are constants which are global to the entire application.
*/
public static final int TEFLON_PORT = 1337;
public static final byte[] TEFLON_RECV_ADDRESS = new byte[] { 0, 0, 0, 0 };
public static final byte[] TEFLON_SEND_ADDRESS = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
public static final int IO_TIMEOUT_MS = 50;
public static final int INPUT_BUFFER_LEN = 1024;
public static final Charset UTF8_CHARSET = Charset.forName("UTF-8");

/*
* These are are application global variables. A reference to each thread and
* a boolean maintaining whether or not the application is alive. These
* references are maintained here so that each handler can easily and
* elegantly obtain a reference to any other handlers, as well as being able
* to signal application exit uniformly.
*/
private RemoteHandler remoteHandler;
private LocalHandler localHandler;
private Thread remoteHandlerThread;
private Thread localHandlerThread;
private boolean alive;

/*
* This is an application global helper routine which is used to signal a
* bug. I pass all unanticipated exceptions here which makes fixing bugs a
* lot easier.
*/
public static void reportException(Exception ex) {
System.err.println("ERROR: " + Thread.currentThread() + " : " + ex);
ex.printStackTrace();
System.err.println("there was a fatal error. please report it. aborting.");
System.exit(1);
}

/*
* This is an application global helper routine which is used to report an
* arbitrary string along with the thread which emanated it. It's very
* convenient to have all debugging output statements pass through a
* centralized gate so that they might be easily disabled in a release build.
*/
public static void debugMessage(String message) {
System.out.println("DEBUG: " + Thread.currentThread() + " : " + message);
}

/*
* This is the application entry point. Note that the application's command
* line arguments are currently ignored.
*/
public static void main(String args[]) {
new Teflon();
}

/*
* This is a utility function to convert a string of bytes into a java
* string. This is necessary because strings need to be converted to and from
* a byte representation in order to pass over the network. The java virtual
* machine takes care of abstractions at the transport layer, but you still
* need to care about the character set.
*/
public static String decodeUTF8(byte[] bytes) {
return new String(bytes, UTF8_CHARSET);
}

/*
* This function is a simple inverse of decodeUTF8.
*/
public static byte[] encodeUTF8(String string) {
return string.getBytes(UTF8_CHARSET);
}
LinkedBlockingQueue<TeflonMessage> incomingMsgQueue = new LinkedBlockingQueue<TeflonMessage>();
LinkedBlockingQueue<TeflonMessage> outgoingMsgQueue = new LinkedBlockingQueue<TeflonMessage>();

/*
* This function returns a reference to the remote handler, which is useful
* when another child of Teflon wants to be able to communicate with the
* remote handler, for instance the local handler.
*/
public RemoteHandler remote() {
return remoteHandler;
}

/*
* This function returns a reference to the local handler, it is useful for
* the same reasons the remote() function is useful.
*/
public LocalHandler local() {
return localHandler;
}

/*
* This function returns a boolean which the caller interprets to mean that
* it should keep executing. Note that this is a synchronized method which
* means that all invocations of this method on the same instance of this
* class will block each other. It is used by the handler threads to check
* that they should continue executing.
*/
public synchronized boolean alive() {
return alive;
}

/*
* This function sets the alive flag to false, effectively signaling to all
* the parts of the application that they should initiate an orderly
* shutdown. It is used by the handlers when they wish to signal termination.
*/
public synchronized void kill() {
alive = false;
remoteHandlerThread.interrupt();
localHandlerThread.interrupt();
}
TeflonFrame teflonFrame = new TeflonFrame(outgoingMsgQueue);
teflonFrame.setVisible(true);

/*
* This is the constructor of the application class. It creates instances of
* the local and remote handlers and passes them an instance of itself, so
* that they may easily acquire references to each other, and any other
* handlers that may be introduced in the future. Note that the handlers all
* implement Runnable and are started in their own threads, which means that
* they execute in parallel.
*/
public Teflon() {
alive = true;
remoteHandler = new RemoteHandler(this);
localHandler = new LocalHandler(this);
remoteHandlerThread = new Thread(remoteHandler, "UDP I/O Thread");
localHandlerThread = new Thread(localHandler, "UI Helper Thread");
SwingTransferRunnable swingTransferRunnable = new SwingTransferRunnable(teflonFrame,
incomingMsgQueue, outgoingMsgQueue);
Thread swingTransferThread = new Thread(swingTransferRunnable, "UI Synchronizer Thread");

remoteHandlerThread.start();
localHandlerThread.start();
UDPTransferRunnable udpTransferRunnable = new UDPTransferRunnable(incomingMsgQueue,
outgoingMsgQueue);
Thread udpTransferThread = new Thread(udpTransferRunnable, "UDP I/O Thread");

try {
remoteHandlerThread.join();
localHandlerThread.join();
} catch (InterruptedException ie) {
reportException(ie);
swingTransferThread.start();
udpTransferThread.start();
udpTransferThread.join();
swingTransferThread.join();
} catch (InterruptedException exc) {
exc.printStackTrace();
}

debugMessage("main thread exiting");
}
}
41 changes: 41 additions & 0 deletions Teflon/src/com/megafrock/teflon/data/TeflonMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.megafrock.teflon.data;

import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Random;

import com.megafrock.teflon.Teflon;

public final class TeflonMessage implements Serializable {
private static final long serialVersionUID = 1L;
private final int sender_id;
private final String body;

public TeflonMessage(int sender_id, String body) {
this.sender_id = sender_id;
this.body = body;
}

public TeflonMessage(String body) {
this(simpleSenderId(), body);
}

@Override
public String toString() {
return Integer.toHexString(sender_id) + " >> " + body.toString();
}

/*
* this is a cheap way to generate reasonably good numeric identifiers for
* hosts
*/
private static int simpleSenderId() {
try {
return InetAddress.getLocalHost().getHostName().hashCode();
} catch (UnknownHostException uhe) {
Teflon.debugMessage("there was a problem retrieving your hostname. falling back to random id.");
return new Random().nextInt(Integer.MAX_VALUE);
}
}
}
Loading

0 comments on commit f5924c2

Please sign in to comment.