Client/Server Programming with Java Socket
The following material is for a seminar in the course TDC513 (Client/Server Techonologies, fall quarter of 1998/1999, by Clark Elliott). It deals with the application of Java socket programming.
All applications are originally developed by Symantec Visual Café Database Development Edition 2.5; and afterwards, they are scaled down and compiled by Java JDK 1.1.7, except the last application about on-line shop browsing, which needs support from the Symantec Java development package as well as Symantec dbANYWHERE Server 1.1a. Win32 platform is assumed.
Socket Programming in Java
Java provides a very simple way for socket communications. Compared with the various TCP primitives such as connect(), bind(), listen(), accept(), etc., coding in Java is more intuitive and easier. For example, one doesn't need to deal with the very cryptic address structure as programming in C/C++.
The following example shows how a client can communicate with the server.
String host = "shrike.depaul.edu";
int port = 8001;
try {
Socket socket = new Socket(host, port);
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String request = "request";
out.println(request);
out.flush();
String reply = in.readLine();
in.close();
out.close();
socket.close();
} catch (Exception e) {
}The following example shows how the server can respond to a client.
int port = 8001;
try {
ServerSocket s = new ServerSocket(port);
while (true) {
Socket incoming = s.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(incoming.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(incoming.getOutputStream()));
String request = in.readLine();
// processing
String reply = "reply";
out.println(reply);
out.flush();
out.close();
in.close();
incoming.close();
}
} catch (Exception e) {
}Client Application Exploiting Existing Protocols
The following three examples (which use the socket-programming scheme as shown above) illustrate how to customize your own applications with existing protocols.
1. Mail reader using POP3 (mailr)
I was very frustrated when I have to telnet into my email account to read mail. The problem is that the account will automatically disconnect me when I don't type anything for a while. On the other hand, I don't want to use sophisticated mail-processing software such as Eudora or Outlook for various reasons. They may serve as my final mail repository, but I don't want to do anything to my account when I'm on the go. Bottom line: I need a light-weight program that allows me read my mail without hassles.
In that regard, I have this POP3Reader class that provides a prototype for me to access my email account. It uses the POP3 protocol to retrieve mail information. It first log you on using your login/password combination, then it can check the number of messages in the mailbox of that account, and retrieve one message based on the index number you ask for.
To see the details of the on-going communication, you might want to de-comment the line
r.setDebug(true);
The next step is to develop a full-blown application based on this class. My Win32 edition mailr is zipped here for downloading. It first asks for a password to log on, then displays a dialog window to show the mail indices. Selecting one index allows one to see the corresponding message. To show the processing power of it, the message layout logic will get rid of most annoying headers.
The dialog window itself is made to be a thread, so that it can poll the server once in a while to see if there are new messages available. The coding should be intuitively self-explanatory.
As you can see, you can customize this application further to do whatever you want it to do, such as displaying subject, sender information instead of indices, or delete some messages from the account. If it is for your own account, you can also hard-code your password to automate the logon process. Multiple account message retrieval is just another add-on to this application once you know how it works.
2. Virtual list server using SMTP (vlist)
The second application is basically a mail sender. The motif for this application is as follows. I am involved in a student organization that wishes to have her own list server. But we are not able to have one for some reason. What we do is the most common practice, we publish among ourselves a list of all email addresses so that one can send mail to everyone subscribes to it.
There are many problems associated with this practice. First of all, it is not scaleable. When our list grows to 50 members, the header field of each mail is very heavy and ugly. On the other hand, someone posts mail by reply-to-all, while others simply use a short-cut alias name to send to all addresses. These ramifications make the synchronization of current subscribers very difficult. If one wishes to drop off, he/she has to notify everyone else in the list, hoping they will delete the address from their lists. But if someone chooses to reply an old mail, he/she may still receive mail intended for the list. Needless to say, this method causes potential mail abuse, as we all know it.
More involved is the political debate: one's email address should be kept confidential. While the sender must show his/her name, one recipient is not supposed to know who else have also subscribed to this list.
So we evolved our protocol as follows. Each message to be posted should be sent to the so-called administrator; the administrator will then re-send it to each subscriber. This relay solved the problem of synchronizing lists. By using a shell-script that sends the same message while looping over addresses, It is also able to avoid showing irrelevant subscriber's addresses except the recipient's own.
Now, new problem arises. Each message bears administrator's signature instead of the real sender; when people want to reply to the sender, the administrator has to relay that process also. As can be seen, the administrator has to keep track of each message he/she sent out earlier. Also, we have to deal with the problem of uncertain delay.
It occurred to me that I could talk directly to SMTP server, so as to fake the sender's mail address. There came the birth of this SMTPSender class. It uses the SMTP protocol to send a single mail. Surprisingly, you don't need the login/password combination to talk to the server, which behaves more or less like an electronic post office. You drop off your mail, telling the destination and your own address, then it gets sent.
To see the details of the on-going communication, you might want to de-comment the line
s.setDebug(true);
The next step is to develop a full-blown application based on this class. My Win32 edition vlist is zipped here for downloading. The list text has certain formatting requirement, providing the login/password that each subscriber (deliminated by ~ and *), the name of the user (deliminated by ^), the privilege level (deliminated by #), and the email address (deliminated by ").
To use it, the user should first log on to identify him/herself, then based on his/her privilege level, he/she can have access to different lists. The list sets up 4 levels, level 4 is for administrator, level 3 is for privileged subscribers, level 2 is for general subscribers, level 1 is for guest. A guest cannot send mail to anybody. A general subscriber can post mails to all general subscribers or administrator only. A privileged subscriber or the administrator can have additional privilege to post mails to special list of privileged subscribers only.
The list text is the key to the application, which is uploaded to a website with cryptic filename. One can set up a directory that disables directory browsing. As long as people are not aware of the existence of such a file, that information is safe. Finally, the whole URL is coded in the application. By doing this instead of hard-coding subscribers, it is easier to update subscriber information. Finally, the application is distributed to every subscriber, so they can post message without delay.
As shown here, there are multitudes of opportunities to customize it based on this skeleton. One can have more than one address to receive messages; the application can have multiple lists, etc.
Now, there are certain political issues here. As mentioned before, the SMTP server doesn't authenticate mail sender (which is the reason I use it in the first place). However, it is different than post office in that, it is free. I strongly believe that anything that is good and free is always subject to abuse. This feature is also the origin of many spams on the Internet. Now, many SMTP servers have enforced security. A less conservative server will check if the sender is a user in the local network. A very conservative server will not send mail if a mail is sent at a host outside of the domain, even if the sender is truly a user in the local network. However, if you look hard enough, there are few, if not none, servers that don't care. (But I'm not gonna tell ya where.) For a student group whose members belong to the same network domain, there shouldn't be a problem at all to use this application.
3. Using HTTP/CGI without a browser (webprobe/reggie)
This application originated from registering a domain name on the Internet. Usually, one is directed to go to the Internic website, check out the availability of certain domain name(s), then register with them for $70 each. However, most names are taken already, one usually have a bunch of options, but it is time-consuming to type them one after another. We need some kind of batch process to expedite the search.
By studying that page, I realized that the webpage uses CGI to generate a result for each query. I can simply that page further into this.
It might be a mis-concept that one has to use a commercial browser to access a webpage. The truth is just the opposite. This Java application called webprobe illustrates how we can simulate communication between HTTP server and a browser. In the same way, it uses socket communications to retrieve information from a website.
Using webprobe, I was able to study the communication process (to simplify the message exchange), and the structure of the query result (to realize that whenever a name is available, the returned page will have several occurrences of the string "DOMAIN NAME AVAILABLE", or else "DOMAIN NAME TAKEN").
Back to basics, while receiving text from the webserver, I check to see if one of the characteristic strings occur; if so, I will close the socket and proceed to the next query. This method leads to the final application reggie.
So as we can see, we can even customize web-based applications using Java socket programming.
Welcome to the wonderful world of Internet!
Problem-Domain Protocol Development
As we have explored above, we can use Java socket programming to take advantage of existing networking protocols to do our daily chores more efficiently. The next step, of course, is to develop one's own networking protocol to suit the need of the specific problem domain.
There are two approaches down this road. First is to use text-based protocols just like POP3, SMTP, etc. The second is to use binary protocols. However, in the real world, the information that needs to be transmitted is not as restrictive as text, nor as primitive as bit strings. Information is usually packaged in certain well-defined format or structure. In that regard, the need to pass information at a high level is very important. Fortunately, Java brings us object serialization mechanism to meet this need. Virtually all objects can be delivered directly across the network.
Serialization is to write an object (and all the objects that are directly or indirectly referenced) to a stream (file or socket); deserialization is to read an object (and all the objects that are directly or indirectly referenced) from a stream (file or socket). The usual syntax is as follows.
// write
FileOutputStream fout = new FileOutputStream("myfile");
ObjectOutputStream out = new ObjectOutputStream(fout);
MyClass obj = new MyClass();
out.writeObject(obj);
// read
FileInputStream fin = new FileInputStream("myfile");
ObjectInputStream in = new ObjectInputStream(fin);
MyClass obj = (MyClass)in.readObject();All objects that are to be serialized must implement the java.io.Serializable interface. It has no methods and fields, and serves only to identify the semantics of being serializable.
import java.io.Serializable;
public class MyClass implements Serializable {
}Default serialization provided by Java is normally adequate. One only needs to remember that neither transient nor static fields will be serialized, including non-public fields. Nor will be serialized are inherited fields, which means superclass(es) must be serializable or has a constructor without any arguments.
Under certain conditions, customized serialization is necessary, which usually occurs to the first serializable class in the inheritance hierarchy. It is also true for those objects whose default serialization is either inefficient or invalid, such as java.util.Hashtable. In these cases, one has to override two private methods writeObject() and readObject(). One can also implement java.io.Externalizable interface, which is simply extended from java.io.Serializable with these two methods.
Since objects are passed across the network, versioning is very important. In Java, a serialized object contains its fully qualified class name and its accompanied version number, which is a 64-bit number. One can obtain a version number through the use of the standard JDK tool serialver, or explicitly declare a version number as follows.
import java.io.Serializable;
public class MyClass implements Serializable {
static final long serialVersionUID = 9199361937812857794L;
}After the introduction, this page will introduce the last application for web-based on-line shop browsing. The basic idea is that the server will provide information to the client through object serialization over socket. Client will base its presentation on HTTP protocol, but the communications can occur along with the port 80 file transfer.
The architecture for the client can be either fat or thin. A fat client is implemented by Java applet, which handles all user-interaction, when data is needed, it will open a separate socket and connects to the application server. A thin client is implemented by HTML's FORM tags, which provided limited browser-delivered user-interaction, whereas further interaction is passed on to a CGI proxy process at the server side via HTTP's CGI mechanism. This proxy will furnish presentation logic, and delegation information retrieval to the actual application server that hits the database.
Obviously, session control is at the client-side in the fat client design. In the case of thin client, session key is held in the dynamic webpage generated by the CGI proxy. Since CGI proxy is also stateless, it implies that data information has to be transmitted back and forth. Therefore, CGI proxy functions more like an applet at the server side, though far less efficient.
Even though the server will handle two kinds of clients, but surprisingly, the coding is relatively simple. The reason is both communications protocol is the same, be it to/from the fat client or to/from the proxy process for thin client. As a matter of fact, the server only listens to the designated socket port, and it will not differentiate the type of the clients at all.

The diagram above illustrates the overall 3-tier architecture of this application. It is obvious that data logic is in the database, business logic is in the application server, and presentation logic varies according to client types, although the server takes part in presentation logic through the webserver. One note about this view is worth mentioning. The CGI connection between the thin client and the application proxy is really between the webserver and the application proxy. However, it is drawn as such to emphasize the fact that the connection is initiated from the thin client.
This application is packaged at shop. Note, it includes all the source code and database files for illustration purposes. To run it, you need to install Symantec Visual Café Database Development Edition 2.5 as well as dbANYWHERE Server 1.1a. The readme.txt in the zip file provides more details.
Five Questions of This Topic
Java socket programming is based upon the TCP/IP, a.k.a. Internet Suite Protocol. It is very similar to the Unix BSD socket standard.
Serialization is to write an object (and all the objects that are directly or indirectly referenced) to a stream (file or socket), whereas deserialization is the opposite operation. It simplifies the network marshaling of objects so that programmers can concentrate more on other network contingent issues.
Objects to be serialized must extend the interface java.io.Serializable. One should realize that transient nor static fields will not be serialized. Nor will Inherited fields unless their superclass(es) are serilizable or have constructors that have no arguments. If the data integrity of the objects has to do with certain computation, then one needs to implement two methods writeObject() and readObject() to customize the serialization.
There are three tiers in the development of distributed computing environment. Data logic is the tier that handles data storage and retrieval, business logic is the one that makes computing based on the requirement and rules of the problem domain, and presentation logic is the one that concerns the user interface. In a three-tier architecture, all three logic are distinct, where as a two-tier architecture usually combines the data and business logic into a single module.
Fat client refers to the architecture that business or even data logic is embedded at the client side of the distributed software. Thin client refers to the one that the client only handles presentation logic, and under certain conditions, some presentation logic is processed at the server side. Using fat client architecture the client side has more session control power, whereas the thin client is not much more than a dump terminal. Both have virtues under different application requirement.