thread1 sum thread2
3.6 Non-Blocking Servers
3.6.3 Further Details
if (socket != null) socket.close();
}
catch (IOException ioEx) {
System.out.println(
"*** Unable to close socket! ***");
} } }
3.6.3 Further Details
Though the preceding sub-section provides enough information to allow the reader to implement a basic non-blocking server, there are other methods that are often required in more sophisticated implementations. Not all of the remaining methods associated with Java's NIO will be covered in this section, but the reader should find that those that are covered are the only additional ones that are needed for many NIO applications. They are certainly the only methods not already covered that will be needed for implementation of the chat server in Exercise 3.6 at the end of this chapter. In fact, of the six new methods mentioned below, only four are NIO methods. The other two are methods of the String class. In all the examples within this section, buffer is assumed to be a pre-declared ByteBuffer.
Though methods read and write are the usual methods for transferring data to and from buffers, there are occasions when it is necessary to implement I/O at the byte level. This is particularly so when the programmer wishes to place particular values into a buffer or to remove all or part of the data from the buffer in order to carry out further processing on that data (possibly prior to re-writing the processed data back to the buffer). The methods to read and write a single byte from/to a buffer are get and put respectively.
Examples
• byte oneByte = buffer.get();
• buffer.put(anotherByte);
As was stated in the previous sub-section, each ByteBuffer has at its heart an array for storing the data that is to be read from or written to a particular channel.
Sometimes, it is desirable to access the contents of this array directly. Method array of class ByteBuffer allows us to do just this by returning the array of bytes holding the data. For example:
byte[] bufferArray = buffer.array();
If the data that is being transferred is of type String, then we may wish to convert this array of bytes into a String. This may be achieved by using an overloaded form
of the String constructor that has this form:
String(<byteArray>, <offset>, <numBytes>)
In the above signature, 'offset' is an integer specifying the byte number at which to start in the array of bytes, while 'numBytes' specifies the number of bytes from the array that are to be used (counting from position 'offset'). Obviously, we need to know how many bytes of data there are in the array. The reader's first inclination may be to assume that this can be derived from the array's length property.
However, this will not work, since it will simply show the size that was allocated to the ByteBuffer by the programmer, not the number of bytes that have been used. In order to determine how many data bytes have been written to the buffer, one must use the ByteBuffer's position method following the latest writing to the buffer (i.e., before the buffer is 'flipped' for reading).
Example
int numBytes = buffer.position();
byte[] bufferArray = buffer.array();
String dataString =
new String(bufferArray, 0, numBytes);
The above example copies the entire contents of the buffer's array and converts that copy into a String. Another method of the String class that can be very useful when processing data within a ByteBuffer does the opposite of the above. Method getBytes converts a specified String into an array of bytes, which may then be written to the buffer.
Example
String myStringData = "Just an example";
byte[] byteData = myStringData.getBytes();
buffer.put(byteData);
Exercises
3.1 Take a copy of example ThreadHelloCount (Section 3.3.1). Examine the code and then compile and run the program, observing the results.
3.2 Modify the above code to use the alternative method for multithreading (i.e., implementing the Runnable interface). Name your main class RunnableHelloBye and your subsidiary classes Hello and Goodbye respectively. The first should display the message 'Hello!' ten times (with a random delay of 0-2 seconds between consecutive displays), while the second should do the same with the message 'Goodbye!'.
Note that it will NOT be the main class that implements the Runnable interface, but each of the two subsidiary classes.
3.3 Take a copy of ResourceServer (Section 3.5), examine the code and run the program.
3.4 Take a copy of MultiEchoClient (Section 3.3), re-naming it ConsumerClient.
Using this file as a template, modify the code so that the program acts as a client of ResourceServer, as shown in the screenshots at the end of Section 3.5. (Ensure that the user can pass only 0 or 1 to the server.) Test the operation of the server with two clients.
Note that exercises 3.5 and 3.6 (especially the latter) are rather substantial tasks.
3.5 Implement a basic electronic chatroom application that employs a multithreaded server. Both server and client will need to be implemented and brief details of these programs are provided below.
The multithreaded chat server must broadcast each message it receives to all the connected clients, of course. It should also maintain a dynamic list of Socket references associated with those clients. Though you could use an array to hold the list (with an appropriate over-allocation of array cells, to cater for a potentially large number of connections), the use of a Vector object would be much more realistic. (If you are unfamiliar with Vectors, then refer to Section 4.8 in the next chapter.)
The client must be implemented as a GUI that can send and receive messages until it sends the string 'Bye'. A separate thread will be required to receive messages from the server and add them cumulatively to a text area.
The first two things that this thread should do are (i) accept the user's chatroom nickname (probably via an input dialogue box) and (ii) send this name to the server. All other messages should be sent via a text area and associated button. As a simplification, assume that no two clients will select the same nickname.
Note
It is likely that a NoSuchElementException will be generated at the line that reads from the socket's input stream when the user's socket is closed (after
sending 'Bye'), so place this reading line inside a try and have an empty catch.
3.6 Implement the same electronic chatroom application that you did for exercise 3.5 above, but this time using Java's non-blocking I/O on the server. You may very well be able to make use of your original client program, but have the client close its socket only after it has received (and displayed) its own 'Bye' message sent back from the server. You can also now get rid of the code dealing with any NoSuchElementException.
At the server end, you will probably find it useful to maintain two Vectors, the first of these holding references to all SocketChannels of newly-connected clients for which no data has been processed and the second holding references to instances/objects of class ChatUser. Each instance of this class should hold references to the SocketChannel and chatname (a String) associated with an individual chatroom user, with appropriate 'get' methods to retrieve these references. As the first message from a given user (the one holding the user's chatroom nickname) is processed, the user's SocketChannel reference should be removed from the first Vector and a ChatUser instance created and added to the second Vector.
It will probably be desirable to have separate methods to deal with the following:
(i) a user's entry into the chatroom;
(ii) a normal message;
(iii) a user's exit from the chatroom (after sending 'Bye').
Signatures for the first and last of these are shown below.
public static void announceNewUser(
SocketChannel userSocketChannel, ByteBuffer buffer)
public static void announceExit(String name)
The method for processing an ordinary message has been done for you and is shown below.
public static void broadcastMessage(String chatName,
ByteBuffer buffer)
{
String messagePrefix = chatName + ": ";
byte[] messagePrefixBytes = messagePrefix.getBytes();
final byte[] CR = "\n".getBytes();//Carriage return.
try {
int messageSize = buffer.position();
byte[] messageBytes = buffer.array();
byte[] messageBytesCopy = new byte[messageSize];
for (int i=0; i<messageSize; i++) {
messageBytesCopy[i] = messageBytes[i];
}
buffer.clear();
//Concatenate message text onto message prefix...
buffer.put(messagePrefixBytes);
for (int i=0; i<messageSize; i++) {
buffer.put(messageBytesCopy[i]);
}
buffer.put(CR);
SocketChannel chatSocketChannel;
for (ChatUser chatUser:allUsers) {
chatSocketChannel =
chatUser.getSocketChannel();
buffer.flip();
//Write full message (with user's name)...
chatSocketChannel.write(buffer);
} }
catch (IOException ioEx) {
ioEx.printStackTrace();
} }
Learning Objectives
After reading this chapter, you should:
• know how to create and process serial files in Java;
• know how to create and process random access files in Java;
• know how to redirect console input and output to disc files;
• know how to construct GUI-based file-handling programs;
• know how to use command line parameters with Java programs;
• understand the concept and importance of Java's serialisation mechanism and know how to implement it;
• know how to make use of Vectors for convenient packaging of serialised objects.
With all our programs so far, there has been a very fundamental limitation: all data accepted is held only for as long as the program remains active. As soon as the program finishes execution, any data that has been entered and the results of processing such data are thrown away. Of course, for very many real-life applications (banking, stock control, financial accounting, etc.), this limitation is simply not realistic. These applications demand persistent data storage. That is to say, data must be maintained in a permanent state, such that it is available for subsequent further processing. The most common way of providing such persistent storage is to use disc files. Java provides such a facility, with the access to such files being either serial or random. The following sections explain the use of these two file access methods, firstly for non-GUI applications and later for GUI applications.
In addition, the important and often neglected topic of serialisation is covered.