Introduction
One of the most basic network programming tasks you’ll likely face as a Java programmer is performing socket functions. You may have to create a network client that talks to a server via a socket connection. Or, you may have to create a server that listens for socket connections. Either way, sooner or later you’re going to deal with sockets. What is a socket you ask? Think of a socket as the basic communication interface between networked computers. Sockets allow you the programmer to treat the network connection as you would any other I/O. In Java, sockets are the lowest level of network coding.
During the next few paragraphs, we’ll work through some examples of socket programming in Java: a simple client, a simple server that takes one connection at a time, and a server that allows multiple socket connections.
SocketClient: A Simple TCP/IP Socket Client
package bdn;
/* The java.net package contains the basics needed for network operations. */
import java.net.*;
/* The java.io package contains the basics needed for IO operations. */
import java.io.*;
/** The SocketClient class is a simple example of a TCP/IP Socket Client.
*
*/
public class SocketClient {
Let’s start by creating a class called SocketClient. We’ll put this into a package called bdn. The only packages that we’re going to need in this example are java.net and java.io. If you’ve not dealt with the java.net. package before, as it’s name implies, it contains the basic classes and methods you’ll need for network programming (see your JBuilder help files or http://java.sun.com/j2se/1.5.0/docs/api/java/net/package-summary.html for more information).
One of the cool things about java is the consistent use of InputStreams and OutputStreams to read and write I/O, regardless of the device. In other words, you can almost always be assured that if you are reading from any input source, you will use an InputStream...when writing to output sources you'll use an OutputStream. This means reading and writing across a network is almost the same as reading and writing files. For this reason, we need import that java.io package into our program.
Some House Keeping
public static void main(String[] args) {
/** Define a host server */
String host = "localhost";
/** Define a port */
int port = 19999;
StringBuffer instr = new StringBuffer();
String TimeStamp;
System.out.println("SocketClient initialized");
In order to make a socket connection, you need to know a couple of pieces of information. First you need a host to connect to. In this example we’re going to be running the client(s) and the server on the same machine. We define a String host as localhost.
Note: we could have used the TCP/IP address 127.0.0.1 instead of localhost.
The next piece of information we need to know is the TCP/IP port that the program is going to be communicating on. TCP/IP uses ports because it is assumed that servers will be doing more than one network function at a time and ports provide a way to maange this. For example: a server may be serving up web pages (port 80), it may have a FTP (port 21) server, and it may be handling a SMTP mail server (port 25). Ports are assigned by the server. The client needs to know what port to use for a particular service. In the TCP/IP world, each computer with a TCP/IP address has access to 65,535 ports. Keep in mind that ports are not physical devices like a serial, parallel, or USB port. They are an abstraction in the computer’s memory running under the TCP/IP transport layer protocol.
Note: Ports 1 – 1023 are reserved for services such as HTTP, FTP, email, and Telnet.
Now back to the code. We create an int called port. The server we’re going to build later in the article will be listening on port 19999. As a result we initialize port to 19999.
A couple of other items that we define here are a StringBuffer instr to be used for reading our InputStream. We also define a String TimeStamp that we’ll use to communicate with the server. Lastly, we System.out.println() a message to let us know the program has begun…this kind of stuff is certainly not necessary, but I’ve found occasionally logging a program status message gives people a peace of mind that a program’s actually doing something.
Requesting a Socket and Establishing a Connection
try {
/** Obtain an address object of the server */
InetAddress address = InetAddress.getByName(host);
/** Establish a socket connetion */
Socket connection = new Socket(address, port);
/** Instantiate a BufferedOutputStream object */
Now we create a try-catch block. This block is needed because the methods of several classes we’re going to reference here throw exceptions. In our example, we’re primarily concerned with IOExceptions, and will specifically capture that one (in real world situations we’d want to deal with this exception more thoroughly). All other exceptions will be captured with a generic Exception.
Note: You should spend some time reviewing the various javadocs on the classes and methods you use. There are often specific exceptions that you want to catch and deal with. Example: had we built an applet that allowed a person to enter the servers and ports they wanted to connect to, we would have wanted to deal with UnknownHostException in the event they keyed an invalid host.
In order to establish a connection with a server, we must first obtain the server’s 32-bit IP address. We obtain the IP address by invoking the InetAddress.getByName() method. As it describes, we pass this method the name of the host we’re looking to connect to. It returns an InetAddress object address containing the host name/IP address pair (i.e. Using localhost in the getByName() method will return localhost/127.0.0.1 in the InetAddress object).
Once we’ve obtained the InetAddress object, we’re ready to establish a socket connection with our server. We create a Socket called connection by instantiating a new Socket object with the InetAdress object address and our previously created int port. If the server is not responding on the port we’re looking for, we’ll get a “java.netConnectException: Connection refused:..” message.
We’ve established our connection. Now we want to write some information to the server. As mentioned previously, Java treats reading and writing sockets is much like reading and writing files. Subsequently we start by establishing an OutputStream object. In general TCP stacks use buffers to improve performance within the network. And, although it’s not necessary, we might as well use BufferedInputStreams and BufferedOutputStreams when reading and writing data across the network. We instantiate a BufferedOutputStream object bos by requesting an OutputStream from our socket connection.
Writing to the Socket
BufferedOutputStream bos = new BufferedOutputStream(connection.
getOutputStream());
/** Instantiate an OutputStreamWriter object with the optional character
* encoding.
*/
OutputStreamWriter osw = new OutputStreamWriter(bos, "US-ASCII");
We could use the BufferedOutputStream.write() method to write bytes across the socket. I prefer to use OutputStreamWriter objects to write on because I’m usually dealing in multiple platforms and like to control the character encoding. Also, with OutputStreamWriter you can pass objects such as Strings without converting to byte, byte arrays, or int values…ok I’m lazy…so what.
Note: If you don’t handle the character encoding and are reading and writing to an IBM mainframe from a Windows platform, you’ll probably end up with garbage because IBM mainframes tend to encode characters as EBCDIC and Windows encodes characters as ASCII.
We create an OutputStreamWriter osw by instantiating it with our BufferedOutputStream bos and optionally the character encoding US-ASCII.
TimeStamp = new java.util.Date().toString();
String process = "Calling the Socket Server on "+ host + " port " + port +
" at " + TimeStamp + (char) 13;
/** Write across the socket connection and flush the buffer */
osw.write(process);
osw.flush();
As shown above, we’re creating two Strings TimeStamp and process to be written to the server. We call the osw.write() method from our OutputStreamWriter object, passing the String process to it. Please note that we placed a char(13) at the end of process...we’ll use this to let the server know we’re at the end of the data we’re sending. The last item we need to take care of is flushing the buffer. If we don’t do this, then we can’t guarantee that the data will be written across the socket in a timely manner.
Reading from the Socket
/** Instantiate a BufferedInputStream object for reading
/** Instantiate a BufferedInputStream object for reading
* incoming socket streams.
*/
BufferedInputStream bis = new BufferedInputStream(connection.
getInputStream());
/**Instantiate an InputStreamReader with the optional
* character encoding.
*/
InputStreamReader isr = new InputStreamReader(bis, "US-ASCII");
/**Read the socket's InputStream and append to a StringBuffer */
int c;
while ( (c = isr.read()) != 13)
instr.append( (char) c);
/** Close the socket connection. */
connection.close();
System.out.println(instr);
}
catch (IOException f) {
System.out.println("IOException: " + f);
}
catch (Exception g) {
System.out.println("Exception: " + g);
}
}
}
The last thing we want to do is read the server’s response. As mentioned before, most networks buffer socket traffic to improve performance. For this reason we’re using the BufferedInputStream class. We start by instantiating a BufferInputStream object bis, calling the getInputStream() method of our Socket object connection. We instantiate an InputStreamReader object isr, passing our BufferedInputStream object bis and an optional character encoding of US-ASCII.
We create an int c that will be used for reading bytes from the BufferedInputStream. We create a while…loop reading bytes and stuffing them into a StringBuffer object instr until we encounter a char(13), signaling the end of our stream. Once we’ve read the socket, we close the socket and process the information…in this example we’re just going to send it to the console. The last thing we do is create code in our catch block to deal with exceptions.
Now that we’ve created the client, what do we do with it? Compiling and running it will give us the following message:
Figure 1: SocketClient Running without Server
We get this message because we don’t have a server listening on port 19999 and therefore can’t establish a connection with it.
Now it’s time to create the server…
SingleSocketServer: A Server That Process One Socket at a Time
package bdn;
import java.net.*;
import java.io.*;
import java.util.*;
public class SingleSocketServer {
static ServerSocket socket1;
protected final static int port = 19999;
static Socket connection;
static boolean first;
static StringBuffer process;
static String TimeStamp;
We start by importing the same packages we did with our SocketClient class. We set up a few variables. Of note, this time we’re setting up a ServerSocket object called socket1. As its name implies, we use the ServerSocket class to set up a new server. As we’ll see later, ServerSockets can be created to listen on a particular port and accept and deal with incoming sockets. Depending on the type of server we build, we can also process InputStreams and OutputStreams.
public static void main(String[] args) {
try{
socket1 = new ServerSocket(port);
System.out.println("SingleSocketServer Initialized");
int character;
In the main() method, we start with a try…catch block. Next we instantiate a new ServerSocket object socket1 using the port value of 19999…the same port that we were looking to connect to with our SocketClient class. Finally, we send a message to the console to let the world know we’re running.
while (true) {
connection = socket1.accept();
BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
InputStreamReader isr = new InputStreamReader(is);
process = new StringBuffer();
while((character = isr.read()) != 13) {
process.append((char)character);
}
System.out.println(process);
//need to wait 10 seconds for the app to update database
try {
Thread.sleep(10000);
}
catch (Exception e){}
TimeStamp = new java.util.Date().toString();
String returnCode = "SingleSocketServer repsonded at "+ TimeStamp + (char) 13;
BufferedOutputStream os = new BufferedOutputStream(connection.getOutputStream());
OutputStreamWriter osw = new OutputStreamWriter(os, "US-ASCII");
osw.write(returnCode);
osw.flush();
}
}
catch (IOException e) {}
try {
connection.close();
}
catch (IOException e) {}
}
}
Since we’re running a server, we can assume that it’s always ready to accept and process socket connections. Using a while(true)…loop helps us accomplish this task. Within this block of code, we start with the ServerSocket socket1’s accept() method. The accept() method basically stops the flow of the program and waits for an incoming socket connection. When a client connects, our Socket object connection is instantiated. Once this is done, our program continues through the code.
Once a Connection is Made
We start by processing the InputStream coming from our socket. (For details on this see the explanation for InputStreams in the Reading from the Socket section of this article).
Note: I’ve thrown a line of code that says Thread.sleep(10000). All I’m doing here is putting the current thread to sleep for 10 seconds. I added this piece of code is purely for the purpose of demonstrating socket connections. It would not be used in a real-world server application.
After we’ve processed the information in the incoming socket, we want to return some information, in the form of an OutputStream, back to the client. The process here is the same process we used in the SocketClient class (see the section entitled: Writing to the Socket). Once the OutputStream is written, the server is now ready to accept another socket.
Running The SingleSocketServer and SocketClient Classes Together
Let’s look at what happens when we run SingleSocketServer and SocketClient together. Executing the programs in JBuilder X will look like this:
Figure 2: SingleSocketServer After One SocketClient Connection
Figure 3: SocketClient After Connecting with SingleSocketServer
Notice the results. Figure 2 shows messages for the SingleSocketServer class. From the message, we can see that the SockeClient called the server at 20:36:33. Looking at Figure 3 we see the messages for the SocketClient class. The message tells us that the server responded at 20:36:43...10 seconds later. SocketClient has ended and SocketServer is waiting for another connection.
Let’s take the same scenario and add a second instance of SocketClient to the mix (to do this, start SocketClient, and while it’s running start a second instance).
Figure 4: SingleSocketServer with Two SocketClient Instances
Figure 5: First Instance of SocketClient
Figure 6: Second Instance of SocketClient
This time look at the results. Figure 4 shows that SingleSocketServer logs two messages, showing the TimeStamp that each instance of SocketClient. Figure 5 shows the first instance of SocketClient we executed. Notice that the time in Figure 5 (20:38:54) is 10 seconds later than the time in the first message in Figure 4 (20:38:44)...as expected
Now let’s look at the second instance of SocketClient. From the time in Figure 4 we see that the second instance was launched 5 seconds after the first one. However, looking at the time in Figure 6, we can see that SingleSocketServer didn’t respond to the second instance until 10 seconds after it responded to the first instance of SocketClient…13 seconds later. The reason for this is simple. SingleSocketServer is running in a single thread and each incoming socket connection must be completed before the next one can start.
Now, you might be asking, “Why in the world would someone want process only one socket at a time?” Recently I was working on a project where a non-java program had to be launched across a network from a java program. And, only one instance of the non-java program could be running at a time. I used a similar solution to solve that problem. Also, it can be a handy way of keeping multiple instances of the same java program from being launched at the same time. “That’s fine” you say, “but I want my server to accept multiple sockets. How do you do that?”
Well, let’s take a look.
MultipleSocketServer: A Server That Handles Multiple Client Connections
package bdn;
import java.net.*;
import java.io.*;
import java.util.*;
public class MultipleSocketServer implements Runnable {
private Socket connection;
private String TimeStamp;
private int ID;
public static void main(String[] args) {
int port = 19999;
int count = 0;
try{
ServerSocket socket1 = new ServerSocket(port);
System.out.println("MultipleSocketServer Initialized");
while (true) {
Socket connection = socket1.accept();
Runnable runnable = new MultipleSocketServer(connection, ++count);
Thread thread = new Thread(runnable);
thread.start();
}
}
catch (Exception e) {}
}
MultipleSocketServer(Socket s, int i) {
this.connection = s;
this.ID = i;
}
We’re not going to spend much time examining all the code here since we’ve already been through most of it. But, I do want to highlight some things for you. Let’s start with the class statement. We’re introducing a new concept here. This time we’re implementing the Runnable interface. Without diving deep into the details of threads, suffice it to say that a thread represents a single execution of a sequential block of code.
In our previous example, the SingleSocketServer class had a while(true) block that started out by accepting an incoming socket connection. After a connection was received, another connection could not happen until the code looped back to the connection = socket1.accept(); statement. This block code represents a single thread…or at least part of a single thread of code. If we want to take this block of code and make it into many threads…allowing for multiple socket connections...we have a couple of options: extending the implementing the Runnable interface or extending the Thread class. How you decide which one to use is entirely up to your needs. The Thread class has a lot of methods to do various things like control thread behavior. The Runnable interface has a single method run() (Thread has this method too). In this example, we’re only concerned with the run() method…we’ll stick with the Runnable interface.
Let’s continue…
Looking through the main() method, we still have to set up our server to allow for connections on Port 19999, there’s still a while(true) block, and we’re still accepting connections. Now comes the difference. After the connection is made, we instantiate a Runnable objectrunnable using a constructor for MultipleSocketServer that has 2 arguments: a Socket object connection for the socket that we’ve just accepted, and an int count, representing the count of open sockets. The concept here is simple: we create a new socket object for each socket connection, and we keep a count of the number of open connections. Although we’re not going to worry about the number of connections in this example, you could easily use count to limit the number of sockets that could be open at once.
Once we’ve instantiated runnable, we instantiate a new Thread thread by passing runnable to the Thread class. We call the start() method of thread and we’re ready go. Invoking the start() method spawns a new thread and invokes the object’s run() method. The actual work within the thread happens within the run() method. Let’s take a quick look at that now.
The run() Method
public void run() {
try {
BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
InputStreamReader isr = new InputStreamReader(is);
int character;
StringBuffer process = new StringBuffer();
while((character = isr.read()) != 13) {
process.append((char)character);
}
System.out.println(process);
//need to wait 10 seconds to pretend that we're processing something
try {
Thread.sleep(10000);
}
catch (Exception e){}
TimeStamp = new java.util.Date().toString();
String returnCode = "MultipleSocketServer repsonded at "+ TimeStamp + (char) 13;
BufferedOutputStream os = new BufferedOutputStream(connection.getOutputStream());
OutputStreamWriter osw = new OutputStreamWriter(os, "US-ASCII");
osw.write(returnCode);
osw.flush();
}
catch (Exception e) {
System.out.println(e);
}
finally {
try {
connection.close();
}
catch (IOException e){}
}
}
}
The run() method’s responsibility is to run the block of code that we want threaded. In our example the run() method executes the same block of code we built in the SingleSocketServer class. Once again, we read the BufferedInputStream, pretend to process the information, and write a BufferedOutputStream. At the end of the run() method, the thread dies.
Now it’s time to run our MultipleSocketServer. Using the same scenario we did previously. Let’s look at what happens when we run the MultipleSocketServer and two instances of SocketClient.
Figure 7: MultipleSocketServer with Two Instances of SocketClient
Figure 8: First Instance of SocketClient
Figure 9: Second Instance of SocketClient
Looking at the messages in Figure 7 that come from the MultipleSocketServer class, we can see that requests for socket connections were sent by the SocketClient programs within a few seconds of each other. According to the console messages on the first instance of SocketClient in Figure 8, the server responded 10 seconds after the request was sent. Figure 9 shows that the second instance of SocketClient receives a response 10 seconds after it sent the request also, thus allowing us the capability of processing multiple socket connections simultaneously.
Summary
Network programming in Java revolves around sockets. Sockets allow us to communicate between programs across the network. Java’s approach to socket programming allows us to treat socket I/O the same as we do any other I/O…utilizing InputStreams and OutputStreams. Although, the examples presented here are relatively simple, they give you an idea of the power of Java in the Client-Server world.
Next time, we’ll move a level up from socket programming and deal with the basics of calling objects across a network using Java’s Remote Method Invocation (RMI) facility.
About the Author
Rick Proctor has over 20 years experience in the IT industry. He's developed applications on more platforms than he cares to remember. Since 2001 Rick has been Vice President of Information Technology for Thomas Nelson Publishers, Inc. (NYSE: TNM). Rick can be reached at tech_dude@yahoo.com.
The source code for the programs in this article can be found at CodeCentral.