HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

When I read about the opportunity to submit articles to Java Developer's Journal one of the first topics that came to mind concerns the desire to be able to record or log data from a web page on the server. Every day I hear questions about outputting data to the server, be it to simply log user information in a guest book, or to have some other, more critical business data sent to the server for more processing; such as an order form.

Of course, you and I both know this is possible using some sort of CGI (considerably goofy intermediary?) program that will write the file. You could also hook your web server up to a several thousand dollar database back end and use another method to get the data there.

No, what many of us are looking for is a simple way, in Java, to send data to the server. Why in Java and not a CGI program? The simplest answer is so that we can have a cross platform solution written in a simple, stable, OO language. While we're at it we'll plop half of it into a web page so that customers or friends will have easy access from a browser!

Enter DataReceiver, DataSender and DataApplet. All three combine into a simple solution to get some formatted text from an applet in a web page to a file on your web server.

What It Looks Like at a Glance
Your customer fires up his web browser and types in http://your.home.page.here/. He sees a small Java applet (DataApplet) that has some editable TextFields for him to enter his personal information and a request for some product information to the web master (you), as shown in Figure 1. He then clicks the Submit Request' button and the text is saved to a log file on the server. You read the text file and contact the customer regarding his request.

Figure 1
Figure 1

The "Under the Hood" Basics
When the Submit Request' button is clicked, DataApplet gets the text from all the TextField and TextArea objects in the applet and formats it into one long String with commas separating each field. DataSender is responsible for establishing a connection to DataReceiver on the web server and sending it the formatted String. It wasn't too difficult for DataSender to determine which server DataReceiver is on because of applet security restrictions. Currently, an applet can only make a network connection to the server that it was loaded from. DataApplet gets the name of its server using the getDocumentBase().getHost() methods and then passes it to DataSender at the same time it passes it the String to be sent.

Meanwhile, on the server, DataReceiver is waiting for incoming connections. After accepting a connection, it reads the incoming data and writes it to a file. In the example code the file is formatted as a comma delimited text file so that it can easily be imported into your favorite spread sheet or database program (See Figure 2).

Figure 2
Figure 2

Java Sockets and How They Relate to Our Problem
The key to the data transfer is a very useful package included with the Sun JDK called java.net'. The java.net package gives us the essential tools to wait for connections on the DataReceiver side and attempt to establish connections on the DataSender side. Remember, DataApplet only passes the String to DataSender who then does all the work of sending it, as shown in Listing 1.

To break things down further (you may want to peek at the code in Listings 2 and 3) let's talk about sockets. Sockets, simply put, are end points of communication. When using them directly, as I do with DataSender and DataReceiver, we are sending data from one "end" to the other, or from the client socket to the server socket. When programming with sockets I usually think in terms of what I have to do to get the data to the socket on the local side of the link and then how I will handle it on the other side. Whatever happens between here and there is left up to the socket implementation and network conditions. Basically, with sockets, most of the hairy details of network programming are hidden from the programmer.

In Java, sockets are quite elegant, thanks to Sun's abstraction of sockets into the Socket class and the ServerSocket class. The Socket class represents a connection to another Socket that data can be both read from and written to. By using the getInputStream() and getOutputStream() methods with an instance of Socket you can read and write data the way you would anywhere else in Java. ServerSocket, as its name implies, is meant for accepting connections, which coincidentally is accomplished by calling its accept()' method. On the server side, when accept() gets an incoming connection it returns a Socket object that can then be used to read from and write to.

In my example, DataSender uses an instance of the Socket class to connect to DataReceiver, which uses an instance of the ServerSocket class. Let's move on into the code.

On the Receiving End
Following the basic model in Figure 1, let's start with the receiving end. DataReceiver is a simple Java application that has a couple of clearly defined responsibilities. First it must accept connections, possibly many at a time. Then, it must be able to receive and write some data from those connections to a file.

It doesn't seem too difficult, and it isn't! The only tricky part is handling the "possibly many" connections. This is where that almighty concept of threaded programming comes into play. The makers of Java of course have built threads into the language and these threads are exactly what I use in DataReceiver. A thread basically does its work and then dies. A Thread class has a run()' method which is analogous to the main() method of a Java application. The run() method is where a thread's execution starts (and usually finishes).

DataReceiver's main' method creates a ServerSocket object and then starts a loop which calls the ServerSocket's accept() method. Since accept() won't return until an inbound connection is made a loop seemed like the simplest way to always be waiting for a connection. When a connection is made we create a new DataReceiver object and pass it the Socket returned by accept(). Since DataReceiver extends the Java Thread class all we have to do to get it going is call its start() method. When DataReceiver's start() method is called, the loop execution resumes at the top and the DataReceiver object starts running (concurrently) with its run() method (See Listing 2).

Now that we have an inbound connection established and a DataReceiver thread running we have to get the appropriate input stream, read the incoming data and then write it to the file.

DataReceiver's constructor handles creating a DataInputStream object by using the passed Socket's getInputStream() method. This means that any data read from this DataInputStream object will be data that has come in from the network, and hence data that needs to be written to the file. DataInputStream seemed like a good choice for this project because it provides some handy methods for reading the simple data being sent from DataSender (i.e. readLine()).

In the run() method, where thread execution actually begins, we call the DataInputStream's readLine() method. This method will stop reading when an end of line character is reached and then return a String object containing the data read. This String is then passed to the local writeData() method where it is then written to the file. You may notice that the writeData() method is declared with the synchronized' modifier. This is very important to avoid file corruption and general havoc on the server end. The reason being is that there is a good chance that many DataReceiver threads could running concurrently (possibly because of several hits to your web page), our job is to make sure that only one will be writing its data to the file at any given time. When the synchronized keyword is used, Java guarantees that only one thread will be executing that method and others will be queued and have access when it becomes available. You may also want to take note of the FILENAME' constant in DataReceiver.java. This represents a system dependent filename. If your server machine is not running on Windows NT, Windows 95, or does not otherwise understand DOS style directory names you should change this to represent a valid path + filename for your system (See Listing 3).

Sending Data with DataSender
DataSender is quite straightforward. Its constructor takes a String object that represents the server machine it must connect to and a String object that contains the data which it must sent to that server.

The Socket object constructor I use in DataSender takes the above host name and also a port number. The host name argument is the name of the machine that DataReceiver is running on. This could be a DNS name such as inetserver.company.com' or an IP address string such as 155.64.182.122'. The port used with the Socket in DataSender must match the port being used by the ServerSocket in DataReceiver. If they don't match, a connection will not be made, so, be sure that the USE_PORT constants in both DataSender.java and DataReceiver.java match (see the "Additional information" section for more about ports). The Socket, on construction will attempt a connection to the server.

DataSender's constructor also creates a DataOutputStream using the newly created Socket's getOutputStream() method. The DataSender run() method simply uses DataOutputStream's writeBytes() method to write the passed String to the socket. After data has been written to the Socket I close the output stream and the Socket. The run() method then completes and exits the thread.

DataApplet is still around , so if the customer wants to enter some different information, or clicks the "Submit Request" button again another DataSender thread will start and send that data to DataReceiver.

In the example code provided in this article DataApplet manages collecting and formatting the data and then creates the DataSender object to send it. I decided it would be best to have DataSender extend from Java's Thread class so that the class could be used in more advanced situations than DataApplet (I'll leave those to you). Making use of threads in communications also makes good sense because of the wait that could occur when establishing connections or when data is being sent or received. When a thread is handling communications the main execution of the application can continue. This is especially useful in a windowed environment where we might need to accept user input while communications occur in the background.

That About Says It!
Using Java for socket communication is quite elegant. Sockets, once constructed allow us to send data over the network using the standard Java input and output streams via their getInputStream() and getOutputStream() methods. The idea, once socket communication is established, is that we worry about getting the data to the socket on our side and then worry about handling it properly on the other side.

Threads are useful tools to handle communications in the "background" without interrupting the current flow of execution. The key factor to using threads is properly using the synchronized' modifier on thread methods that should only be in use by a single thread at any given time.

A Java Applet can be a quick and easy front end for user input that needs to be sent to the server. Currently, for security reasons applets can only make socket connections to the machine they were loaded from, so, in our example the DataReceiver must be running on the web server machine.

Additional Information (and the Gotchas)
One thing I ran into while coding the examples was a misunderstanding of the DataInputStream's readLine() method. I was under the impression that the data I read would be up to and including the \r\n' characters. Not so, when writing the data to the file and then looking at it with an editor it appeared to be one long continuous line of text. After some playing around, and reading the documentation a little closer I realized that the \r\n' characters were not being read and that I would simply have to provide them in my writeData() method.

The Socket.close() method is currently not working as designed on Win NT and 95 platforms. This didn't have any ill effects in my testing as the sockets eventually timed out and were cleaned up by the system (netstat -a 5' revealed this). This is listed on Sun's known bugs list and should be corrected in a future JDK release.

If you are planning on using the example code with an Internet service provider they must be using an operating system that supports a Java interpreter. This is because DataReceiver is a Java application and must be run directly on the server via the Java interpreter. These days this doesn't seem like a real problem as many OS vendors are rushing to support the wave of Java applications and applets.

Ports
Ports are basically a way of routing incoming connections to their appropriate socket applications. Under normal conditions it is important that only one application use a given port at any one time. So before starting up DataReceiver be sure you know that nothing else is using its port (or the USE_PORT variable in DataReceiver.java is changed accordingly). On UNIX systems this information can usually be found in the /etc/services file. In Win95 it can be found in the windows directory and on Win NT it can be found in the \winnt\system32\drivers\etc directory. This file defines which applications use which ports.

Steps to run the examples as provided: First, be sure that the USE_PORT constants in DataReceiver.java and DataSender.java match. Then be sure the FILENAME constant in DataReceiver.java represents a system dependent path + filename that will work on your system. Compile the source with javac (or your favorite).

To get things running you need put the compiled .class files from DataReceiver, DataSender, and DataApplet in a directory accessible to your web server. Add the <applet> tag provided in DataApplet.html to the .html document your users/customers can access, as shown in Listing 4. Run the DataReceiver application on your server and then load the page that you added the <applet> tag to into your favorite Java aware browser.

About the Author
Andrew is currently a support technician on the Café team at Symantec. He writes network programming examples and research on current Java networking technologies. Before joining the Café team he supported network administration utilities. Andrew can be reached at [email protected]

	

Listing 1: The DataApplet Code.

// FILE: DataApplet.java
// BY:   Andrew Idsinga 7/28/96

// the imports
import java.io.*;
import java.net.*;
import java.awt.*;
import java.applet.*;

public class DataApplet extends Applet{
	// **init method**
	public void init(){
		//{{INIT_CONTROLS
	  setLayout(null);
	  resize(392,222);
	  name=new TextField(22);
	  add(name);
	  name.reshape(7,33,182,26);
	  title=new TextField(22);
	  add(title);
	  title.reshape(196,33,182,26);
	  label1=new Label("Name:");
	  add(label1);
	  label1.reshape(7,7,182,19);
	  label2=new Label("Title:");
	  add(label2);
	  label2.reshape(203,7,182,19);
	  email=new TextField(22);
	  add(email);
	  email.reshape(7,91,182,26);
	  label3=new Label("Email:");
	  add(label3);
	  label3.reshape(7,65,182,20);
	  sendButton=new Button("Submit Request!");
	  add(sendButton);
	  sendButton.reshape(105,189,182,26);
	  label4=new Label("Telephone:");
	  add(label4);
	  label4.reshape(196,65,182,20);
	  telephone=new TextField(22);
	  add(telephone);
	  telephone.reshape(196,91,182,26);
	  prodinfo=new TextField(44);
	  add(prodinfo);
	  prodinfo.reshape(7,156,371,26);
	  label5=new Label("Product Info. Desired:");
	  add(label5);
	  label5.reshape(7,124,182,26);
	  //}}
	}

	// **class instance variables**
	//{{DECLARE_CONTROLS
	TextField name;
	TextField title;
	Label label1;
	Label label2;
	TextField email;
	Label label3;
	Button sendButton;
	Label label4;
	TextField telephone;
	TextField prodinfo;
	Label label5;
	//}}

	// **class methods**
	public void clickedSendButton() {
		// get the formatted text
		String theData = formatData(name.getText()
			+","+title.getText()+","+
			email.getText()+","+telephone.getText()
			+","+prodinfo.getText());
		// for debugging, display the data
		System.out.println("Data: "+theData);
		// Create and start the DataSender thread
		try{
			showStatus("Creating DataSender object");
			String serv = getDocumentBase().getHost();
			showStatus("Server: "+serv);
			System.out.println("Server: "+serv);
			DataSender ds = new DataSender(
				serv, theData);
			ds.start();
		}
		catch(IOException ioe){
			showStatus("Problem creating DataSender "
				+ioe.toString());
		}
  }

	// handles the "Submit Request" button
	public boolean handleEvent(Event event) {
		if (event.id == Event.ACTION_EVENT
			&& event.target == sendButton) {
			clickedSendButton();
			return true;
		}
		return super.handleEvent(event);
	}

	// this method removes carriage returns from
	// the data and adds a single '\r\n' to the
	// end
	private String formatData(String str){
		StringBuffer sb = new StringBuffer(str);
		for(int x = 0; x < sb.length(); x++){
			if(sb.charAt(x) == '\r'){
				sb.setCharAt(x, '\040');
				if(sb.charAt((x+1)) == '\n'){
					sb.setCharAt((x+1), '\040');
				}
			}
		}
		// set the terminating '\r\n'
		sb = sb.append('\r');
		sb = sb.append('\n');
		String retData = new String(sb);
		return retData;
	}
}

Listing 2: DataReceiver.java

// FILE: DataReceiver.java
// BY:   Andrew Idsinga 7/28/96

// the imports
import java.io.*;
import java.net.*;

public class DataReceiver extends Thread{
	// **main method**
	public static void main(String args[]){
		System.out.println("Starting DataReciever");
		System.out.println("Current log file: "
			+FILENAME);
		// create the ServerSocket object
		ServerSocket ss = null;
		try{
			 System.out.println(
			 	"Creating ServerSocket");
			 ss = new ServerSocket(USE_PORT);
		}
		catch(IOException ioe){
			System.out.println("Could not create"
				+" ServerSocket ss"+ioe.toString());
			System.exit(1);
		}
		// loop waiting for inbound connections
		try{
			while(true){
				Socket s = ss.accept();
				System.out.println(
					"Connection from: "+
					s.getInetAddress().toString());
				// create and start a DataReceiver
				// thread to accept incoming data
				DataReceiver dr = new DataReceiver(
					s, FILENAME);
				dr.start();
			}
		}
		catch(IOException ioe){
			System.out.println("Problem in accept loop"
				+ioe.toString());
		}
	}

	// **class constant variables**
	// the port to be used for incoming connections
	private static final int		USE_PORT = 6969;
	// the system dependant filename which data
	// will be written to
	private static final String	FILENAME =
		".\\dataread.log";

	// **class instance variables**
	// the Socket object used for communications
	Socket s;
	// The file to be written to
	String fileName;
	// input streams for reading from the Socket
	DataInputStream dis;

	// **class constructor**
	public DataReceiver(Socket theSock,
		String theFileName){

		fileName = theFileName;
		s = theSock;
		try{
			dis = new DataInputStream(
				s.getInputStream());
		}
		catch(IOException ioe){
			System.out.println(ioe.toString());
		}
	}

	// **the thread's run method**
	public void run(){
		try{
			// read and write the data
			String readStr = dis.readLine();
			writeData(readStr);
			// close the iinput streams and the socket
			dis.close();
			s.close();
		}
		catch(IOException ioe){
			System.out.println("Problem reading"
			+" from Socket s "+ioe.toString());
			return;
		}
	}

	// **class methods**
	// this method os called from the run() method
	// to write the data to the file
	private synchronized void writeData(
		String data){

		try{
			// open the file for writing
			RandomAccessFile file =
				new RandomAccessFile(FILENAME, "rw");
			// seek to the end of the file
			file.seek(file.length());

			// write the data
			file.writeBytes(data);
			// write a '\r\n' (for dos editors)
			file.writeByte('\r');
			file.writeByte('\n');
			// close the file
			file.close();
		}
		catch(IOException ioe){
			System.out.println("Problems writing, "+
				ioe.toString());
		}

		System.out.println("Press CTRL-C "+
			"to quit DataReciever");
		return;
	}
}

Listing 3: DataSender.java

// FILE: DataSender.java
// BY:   Andrew Idsinga

// the imports
import java.io.*;
import java.net.*;

public class DataSender extends Thread{
	// **the thread's run method**
	public void run(){
		System.out.println("DataSender, run()");
		try{
			dos.writeBytes(theData);
			// close the output stream and the socket
			dos.close();
			clntSock.close();
		}
		catch(IOException ioe){
			System.out.println(ioe.toString());
			return;
		}
	}

	// **class constant variables**
	// the same port that is used on the server
	private static final int USE_PORT = 6969;

	// **class instance variables**
	Socket clntSock;
	String servStr;
	String theData;
	DataOutputStream dos;

	// **class constructor(s)**
	public DataSender(String theServer,
		String data)throws IOException{
		// for debugging, display some data
		System.out.println(
			"DataSender, constructor");
		// assign local references
		servStr = theServer;
		theData = data;

		// create and connect the socket
		// to the server
		//       (the server name, the port to use)
		clntSock = new Socket(servStr, USE_PORT);

		// get the output stream so we can write
		// data to the socket
		dos = new DataOutputStream(
			clntSock.getOutputStream());
	}
}

Listing 4: The DataApplet HTML.

<html>
<body>

<applet code="DataApplet.class" width=392 height=222>
You must have a Java enabled browser for DataApplet!
</applet>

</body>
</html>


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.