First time here? Please, start at the beginning.
Showing posts with label uChat. Show all posts
Showing posts with label uChat. Show all posts

Tuesday, September 7

uChat as an FSM

No, not a Flying Spaghetti Monster, a Finite State Machine.

Wikipedia's article is rather long and dry. To put it briefly, a finite state machine is a device (or program) that has a set number of "states". An example would be a light switch: it has 2 states, "on" and "off". A dimmer switch, to contrast, would be an Infinite State Machine: it has a (nearly) infinite number of positions.

It addition to its set of states, a state machine also has "transition conditions" that cause it to change its state and (usually) a set of reactions that change depending on its state.

Here's another example, you could model a body of water as an FSM. It's states would be "solid", "liquid" and "gas". Its transition conditions would be defined as temperature changes, and its reactions would change depending on its state. If you poke a piece of ice sitting on a table, it will likely slide away; however, if you poke the surface of a pool of water, it will ripple. A slightly odd example, but I believe it serves its purpose.

Virtually every computer program you interact with is a Finite State Machine. Some have obvious states, others are more subtle, but few complex programs can be written without maintaining an internal state and altering its reactions to user input accordingly.

"But," you ask, "how is a simple program like uChat a Finite State Machine?" It may not be obvious from a glance at the source, but it is. A simple one, granted, with only 2 states: "connected" and "disconnected".

Fun fact: this picture's filesize is larger than uChat's source.

Lets list uChat's input sources, they're all part of the UI: the Name field, the Join button, the Message field, and the Send button.

The text area is marked an non-editable, so no user input can come from it. Also, clicking the Send button has the same effect as pressing enter in the Message field. There's a similar relation exists between the Name field and the Join button.

So, simplified slightly to "user intents" (actions the user intends to perform) we now have: "join the chat" (connect), "leave the chat" (disconnect), and "send a message".

The program's possible responses are: join the chat, leave the chat, send a message, ignore.

This post is incomplete. I may or may not return to this subject later, but I believe I've explained sufficiently for you to understand where I was going.

Wednesday, August 25

uChat: Network Protocol

(I think I'm going to abandon the "Part X.Y" thing, for now at least.)

As mentioned once before, uChat uses UDP broadcast packets to communicate. This isn't exactly ideal: every computer on the network can "listen in" if they listen on the right port. But it's an easy way to communicate without the need for a central server.

How do you send a UDP broadcast? It's dead-simple: just set the destination IP address of your packets to 255.255.255.255, the broadcast address. These packets will be discarded by a router, so we're not clogging up the entire Internet, just the local LAN.

By the way, the port number used is 12121. Just a randomly chosen number above 1024 (Unix-like systems generally require root access to use ports below 1024). Any port would do really, as long as everyone you want to talk to uses the same one.

Now, just what do these packets say? The content of each packet is a 4 character code followed by the message. The current version uses only 4 message types, but more could be added without trouble as the program discards a packet that it doesn't understand.

Here's the message types and their meanings:
  • "SAY:": this one should be self explanatory, the message is what the sender of the packet "said"
  • "JOIN": sent when someone clicks the "join" button, it announces that someone has joined the chat, the message is the name/alias entered in the upper text area
  • "LEAV": reverse of "JOIN", message is also the name/alias
  • "HERE": sent as a response to a "JOIN" packet, it informs the joinee of who is already present, the message is, again, the sender's name/alias

Why so many devoted to keeping track of who's present? Because the "SAY:" packet doesn't include the name of the sender, only the IP address. Each instance of uChat is responsible for maintaining an IP-address-to-name mapping table (the lookupTable variable).

That's really it. Simple, eh? You can watch it in action with a program like WireShark (I've never tried that myself, but there's no reason it wouldn't work), or by running netcat on another machine with: "nc -lup 12121".

Monday, August 23

Part 2.1: uChat Source explained (mostly)

Here's the (mostly) annotated and explained source for uChat. The really rough parts are left out for now, they'll get their own posts a bit later.

I can't stress enough the importance of copy-pasting this into your editor of choice, the word wrap is going to make this impossible to dechpher. I'll see what I can do about the site layout.

//lots of imports here
import java.util.*; // name-IP address map
import java.net.*;  // several Socket classes and InetAddress
import java.io.*;   // IOException and a couple *Writers

import java.awt.*;       // GUI elements
import java.awt.event.*; // GUI event handlers

import java.util.regex.*; // regular expression library, used to prevent a minor exploit

// changed the class/file name, class names should usually start with a capital
// subclass java.awt.Frame (a window) so we can be displayed on-screen
// implement WindowListener and ActionListener (from java.awt.event) so we can handle our own events
// implement java.lang.Runnable so we can listen for incoming messages in a seperate thread
// (I've reciently realized multi-threading wasn't actually necessary...)
public class uChat extends Frame implements WindowListener, ActionListener, Runnable {

// NOTE: within source code, I tend to use "us", "we", "our", etc. to refer to the class/program and it's actions. Don't ask why, I don't know.

 // network-related vars
 InetAddress addrs;   // broadcast IP address, declared below
 int port = 12121;    // communication port number
 DatagramSocket sock; // multi-purpose UDP socket

 // GUI elements
 TextField name;  // top text field
 TextArea disp;   // main text area
 TextField input; // bottom text field
 Button send;     // obvious 
 Button connect;  // not really "connecting" but close enough

 Map lookupTable; // keeps track of which user(name) is at which IP address

// constructor, throws any Exception because I was too lazy to list each one that could happen
// NOT A BEST PRACTICE
public uChat() throws Exception {
 super("uChat"); // call superclass constructor: Frame(String s); sets window title

 // retrieve broadcast IP address, method may (but shouldn't with a numerical address) throw UnknownHostException
 addrs = InetAddress.getByName("255.255.255.255");

 // initialze Socket, and bind to the port
 sock = new DatagramSocket(port);

 // initialize the lookup table as a HashMap
 lookupTable = new HashMap();

 // set up GUI elements
 disp = new TextArea("",0,0,TextArea.SCROLLBARS_VERTICAL_ONLY); // no starting text, default height and width, scrollbar display policy
 input = new TextField();
 name = new TextField();
 send = new Button("Send");    // buttons need
 connect = new Button("Join"); // labels

 // prevent editing the message display box
 disp.setEditable(false);

 //GUI layout design can be pretty hairy

 // a java.awt.Panel is a simple container
 // constructor accept java.awt.LayoutManager as an arg, BorderLayout is a subtype
 Panel pnl1 = new Panel(new BorderLayout());

 // add an anonymous label, followed by our name field and connect button
 pnl1.add(new Label("Name:"), BorderLayout.WEST);
 pnl1.add(name, BorderLayout.CENTER);
 pnl1.add(connect, BorderLayout.EAST);

 // the lower Panel, with the input field and send button
 Panel pnl2 = new Panel(new BorderLayout());
 pnl2.add(input,BorderLayout.CENTER);
 pnl2.add(send,BorderLayout.EAST);

 // set the our layout and add the panels and display area
 setLayout(new BorderLayout());
 add(pnl1, BorderLayout.NORTH);
 add(disp, BorderLayout.CENTER);
 add(pnl2, BorderLayout.SOUTH);

 // set up a new thread and pass us as the Runnable
 Thread t = new Thread(this);
 // start the thread
 t.start();

 // add us as ActionListener for the main GUI elements
 send.addActionListener(this);
 connect.addActionListener(this);
 input.addActionListener(this);
 name.addActionListener(this);

 // add us as a WindowListener for ourself
 addWindowListener(this);

 // display the main window
 setVisible(true);
} // end of constructor

// override Frame.setVisible(boolean b) to add some functionallity
public void setVisible(boolean b) {
 super.setVisible(b); // call Frame.setVisible() to actually show/hide the window
 if(b) { // if we're being shown...
  pack(); // ... shrink/grow the window so that each element gets it's 'prefferred size"
  Dimension scrn = Toolkit.getDefaultToolkit().getScreenSize(); // get the screen's size (in pixels) and...
  // ... calculater a point exactly our size up and left from the bottom corner of the screen and move us there
  setLocation((scrn.width-this.getSize().width)/1,(scrn.height-this.getSize().height)/1);
  // the result of this is causing the window to appear in the lower-right corner of the screen
 }
} // end of setVisible

public static void main(String arg[]) { // this is actually a stub,
 // call the constructor (from inside a try block), but don't keep a reference to the created object
 // (it'll dislay itself, so we don't need to)
 try { new uChat(); }
 // catch any Exception that pops up and pass it to the handle(Excpetion) method (it's way below)
 catch(Exception e) { handle(e); }
} // end of main

// a beast of a method here, this is from the ActionListener interface
// it's called any time an ActionEvent occurs, like clicking a button or pressing enter in a text field
// it's pretty convoluted. I'll devote an entire post (or more) to explaining it later.
public void actionPerformed(ActionEvent evt) { /* left out for now */ }

// a "flag" to tell the listener thread to stop
boolean die = false;

// the run() method is from the java.lang.Runnable interface
// this code will run in a seperate thread
// it listens for incoming UDP packets, interprets them, and updated the display text area accordingly
// it's fairly complicated to, and will get it's own post later
public void run() {
 // this loop does the hard part
 while(!die) { /* left out for now */ }
 // we've been told to stop, but we have a little clean up to do
 try {
  sock.close(); // close the socket
 } catch(Exception e) { handle(e); }
}

// from the WindowListener interface, this method is called when the user asks the window to close by clicking it's exit button
// it does a little clean-up: announcing to other uChaters that we're leaving
public void windowClosing(WindowEvent evt) {
 setVisible(false); // hide the window

 // if we're connected
 if(connect.getLabel() == "Join") { //only send LEAV if we're JOINed
  try {
   //build and send packet announcing that we're leaving
   byte[] data = "LEAV".concat(name.getText()).getBytes("ISO-8859-1");
   DatagramPacket out = new DatagramPacket(data, data.length,addrs,port);
   sock.send(out);
  } catch(Exception e) { handle(e); } // if an exception occurs, pass it to handle(Exception)
 }

 // change the flag, telling the listener thread to stop
 die = true;

 // this frees all resources associated with this window, allowing the event-processing threads to stop
 dispose();

 // this method explicitly tells the JMV to exit, but isn't needed as all threads should be ready to stop
// System.exit(0);
}

// these are also part of the WindowListner interface, but aren't used by this program
public void windowOpened(WindowEvent evt) { }
public void windowClosed(WindowEvent evt) { }
public void windowIconified(WindowEvent evt) { }
public void windowDeiconified(WindowEvent evt) { }
public void windowActivated(WindowEvent evt) { }
public void windowDeactivated(WindowEvent evt) { }

// all purpose Exception handler.
// NOT REMOTELY A BEST PRACTICE but sufficient for this small program
// static, so it can be accessed from main()
public static void handle(Exception e) {
 // set up a pair of writers to convert the Excpetion's stack trace to a string
 StringWriter sb = new StringWriter();
 PrintWriter out = new PrintWriter(sb);

 // dump the stack trace to the above writer and System.out (aka STDOUT, aka the terminal window)
 e.printStackTrace(out);
 e.printStackTrace(System.out);

 // make a new text area, default sizes, with the stack trace (via the writers) as its content
 TextArea disp = new TextArea(sb.getBuffer().toString());

 // simple frame, set its layout
 Frame f0 = new Frame("Oops");
 f0.setLayout(new BorderLayout());

 // anonymous inner class as event listener for the frame, more in another post
 f0.addWindowListener(new WindowAdapter() {
  public void windowClosing(WindowEvent evt) { ((Window)evt.getSource()).dispose(); }
 });

 // add a label, then add the text area from above
 f0.add(new Label("Oops, something went wrong. uC may or may not still work."), BorderLayout.NORTH);
 f0.add(disp, BorderLayout.CENTER);

 // pack() and show the frame
 f0.pack();
 f0.setVisible(true);
 // if you crash uChat (hint: start another instance while one's running), you'll see this method
 // in action. it's not a pretty error message, and would tell your average user absolutely nothing.
 // to be honest, this was 75% to shut the compiler up about "uncaught exceptions", 25% for debugging
}

}

Hopefully, with what you already know and the comments to help with what you don't, most of that makes at least some sense.

Not sure what I'll cover next... there's 3 or 4 topics that need to be covered. I should probably talk about multi-threading; there's a fair bit of network communication going on (that'll be at least 2 posts); I could go into how the interface goes together; need to touch on inner classes, maybe exception handling.

Anyway, that should be easier to understand than what I'd posted before. I'll update the top-bar page in a couple minutes.