Java - Networking Java - Networking [ Server Program | Client Program | Running the Programs | Using URLs ] Introduction Java includes a number of classes and packages to support the writing of Internet aware programs. In this workshop we look at how to write a simple client/server program, and how to download a web page using the URL class. Basic client-server example Here is a simple example of client-server operation using two very simple Java programs. There are two programs, Server.java and Client.java. The server does nothing more than return the input message with all lowercase letters converted to uppercase. Here's the code of the server program: import java.io.*; import java.net.*; public class Server { public static void main(String args[]) { String messageIn; String messageOut; try { ServerSocket ssock = new ServerSocket(6789); while(true) { Socket connsock = ssock.accept(); InputStreamReader inStr = new InputStreamReader(connsock.getInputStream()); BufferedReader inNet = new BufferedReader(inStr); DataOutputStream outNet = new DataOutputStream(connsock.getOutputStream()); messageIn = inNet.readLine(); messageOut = messageIn.toUpperCase() + "\n"; outNet.writeBytes(messageOut); } } catch(IOException e) { System.out.println(e.getMessage()); } } } Note the inclusion of the package java.net. This package contains most of the classes supporting networking. Any network program is likely to encounter input/output errors of various sorts, so most of the code is inside a try block. There is only a single catch block to catch all of the I/O exceptions; a better program would include separate catch blocks for each exception and give more detailed error messages. In this simple example the server will be listening on port 6789. Any port number could be used as long as it is not already in use on the local machine. The code ServerSocket ssock = new ServerSocket(6789); creates a socket listening on that port. Note that Java has two distinct socket classes ServerSocket and Socket. This distinction hides a number of socket initialisation issues. Once the socket has been created the server will be listening. The practical implication of this is that the accept() method of the ServerSocket class will not return until a connection request has been received and accepted. This is called blocking behaviour. The accepted connection request results in accept() returning a Socket object. This has methods associated with the transfer of data. Input of data For a simple application such as that implemented by this program, a BufferedReader object is required. This should be familiar from the earlier file handling examples. However the "native" input object associated with a Socket is an InputStream (from the java.io package). It is necessary to "wrap" this to provide a BufferedReader since an InputStream is just an arbitrary sequence of bytes. This is a two stage process: construct an InputStreamReader object. In this example this is called inStr. Its constructor requires an InputStream as parameter. An InputStreamReader will convert the incoming byte stream into a character stream (remember that Java characters are 16 bits, not 8 bits) converting locale specific character encodings to Unicode. (The InputStream class is actually abstract; here this means that you cannot instantiate it but instances are defined within the Socket class). InputStreamReader inStr = new InputStreamReader(connsock.getInputStream()); construct a BufferedReader object. In this example this is called inNet. Its constructor requires an InputStreamReader as parameter. Once a BufferedReader is associated with the Socket, the readLine() method can be used to get the input messages line-by-line. This assumes that the client has written them in such a fashion with the messages suitably terminated. (In this simple example, the client only ever writes one message). BufferedReader inNet = new BufferedReader(inStr); Output of data The requirements here are very similar to those of input. The "native" output method for a Socket is an OutputStream (also from the java.io package). This also requires wrapping. A DataOutputStream object called outNet is used. DataOutputStream outNet = new DataOutputStream(connsock.getOutputStream()); The DataOutputStream class is used for "binary" or "transparent" output of raw data. However this is not suitable for sending characters back to the client as they will be sent in Java 16-bit format rather than the 8-bit bytes that a client might expect. To overcome this problem the writeBytes() method of the DataOutputStream class is used. This takes a String as parameter and writes a suitable number of bytes to the Socket. outNet.writeBytes(messageOut); Processing the data The core of the program that actually reads in the input messages, processes them and writes the processed messages back to the client is the following three lines of code: messageIn = inNet.readLine(); messageOut = message.toUpperCase() + "\n"; outNet.writeBytes(messageOut); This is quite straightforward and doesn't require any further discussion. The client program The client code is quite simple and has many similarities to the server code. Here is the code. import java.io.*; import java.net.*; public class Client { public static void main(String[] args) { String message = null; try { BufferedReader kbd = new BufferedReader(new InputStreamReader(System.in)); Socket csock = new Socket("localhost", 6789); DataOutputStream outNet = new DataOutputStream(csock.getOutputStream()); BufferedReader inNet = new BufferedReader(new InputStreamReader(csock.getInputStream())); message = kbd.readLine(); outNet.writeBytes(message + "\n"); message = inNet.readLine(); csock.close(); System.out.println("Server sent: " + message); } catch(IOException e) { System.out.println(e.getMessage()); } } } Input of data This program has two data input channels, one reads from the standard input, the other reads from the Socket. BufferedReader objects are associated with both input channels; they are called kbd and inNet respectively. The program also has two output channels. The most interesting one is associated with the Socket called csock and the DataOutputStream object is called outNet. The first step in setting up the communication from the client to the server is to construct the Socket object csock. There are several constructors for the Socket class; in this case the variant with String and int parameters is used. Socket csock = new Socket("localhost", 6789); The first parameter of the constructor is the name of the remote host on which the server is running, or alternatively its IP address. Initially we will run both the server and client on the same machine for test purposes, so we use the name "localhost" (or "127.0.0.1"). The second constructor parameter is, of course, the port number. If the server process is not running and listening on port 6789 then the Socket constructor will throw an exception in the usual manner. If the Socket constructor does not throw an exception then a connection has been established with the server and the interchange of messages will proceed in a fairly obvious fashion. Running the programs In order to run the client program the server must first be operating on the correct host and listening on the correct port. To test the programs by running them both on the same machine, first open a command window and run the server by typing the command: java Server No further operating system prompt will be seen. The server process is now running. Now open a second command window and run the client. Type a message, press enter, and the client-server interaction will be demonstrated in the following manner: C:\> java Client Hello World Server sent: HELLO WORLD If the server is not running, the client will produce an error message. There are many ways in which both the client and server programs could be improved. For example, the various exceptions could be caught and informative messages given. The server hostname and port could be supplied to the client as command line arguments. The server could log requests and the client could allow multiple messages. Another major problem with this simple server is that it can only serve one client at a time. To get round this the server should create a new process and hand-off the client's request to this process. The server then goes back to listening on the original port for the next request. This involves concurrent programming and is achieved by the use of threads. The next step is to run the server and client programs on separate networked machines. To do this you must change the hostname used in the client program to the IP address of the machine that the server is running on. Security issues When running these programs across the network in the university labs, you may experience problems due to security restrictions on the machines. See this week's exercise sheet for up-to-date information on this, or your tutor. Using URLs The methods of the URL class provide a simple way to obtain content from those types of servers whose functionality is associated with one of the standard methods. Here is a simple example that will fetch a WWW page and display the HTML. import java.io.*; import java.net.*; public class GetURL { public static void main(String args[]) { int lineCount = 0; String line; try { URL url = new URL(args[0]); InputStream in = url.openStream(); InputStreamReader inStr = new InputStreamReader(in); BufferedReader inNet = new BufferedReader(inStr); while((line = inNet.readLine()) != null) { lineCount++; System.out.println(line); } System.out.println("Lines read = " + lineCount); } catch(IOException e) { System.out.println(e.getMessage()); } } } And here's an example of it being used (with some of the output lines suppressed). C:\>javac GetURL.java C:\>java GetURL http://www.scit.wlv.ac.uk/~in8297 Mel Ralph Home Page
Mel Ralph - SCIT
. . Output omitted . .
Lines read = 35 This is all very straightforward and apart from the use of the URL class and the associated method openStream() should be completely familiar. URL is a high-level class that enables a connection to a server to be established with the minimum amount of code. All of the detail, such as creating sockets, is hidden inside the class. However, an object of type URL will have limited functionality. The URL class is also useful for validating a url string entered by the user and for parsing the different parts of the url, as this simple program demonstrates: import java.net.MalformedURLException; import java.net.URL; public class DisplayURL { public static void main(String[] args) { URL url = null; try { url = new URL(args[0]); } catch(MalformedURLException e) { System.out.println("Bad url"); System.exit(1); } System.out.println(url); System.out.println("Protocol: "+url.getProtocol()); System.out.println("Path: "+url.getPath()); System.out.println("Host: "+url.getHost()); System.out.println("Port: "+url.getPort()); System.out.println("Query: "+url.getQuery()); System.out.println("File: "+url.getFile()); } } producing this output: C:\>java DisplayURL http://www.wlv.ac.uk:80/~in8297/index.html http://www.wlv.ac.uk:80/~in8297/index.html Protocol: http Path: /~in8297/index.html Host: www.wlv.ac.uk Port: 80 Query: null File: /~in8297/index.html References Computer Networking - A Top-down approach featuring the Internet by J.F.Kurose and K.W.Ross. (Addison-Wesley, 2005) This page is part of a set of notes about the Java programming language prepared by Peter Burden for the module CP4044. A disclaimer applies to this page.