The NIO API provided from Java 1.4 is often used to develop high-performance web servers. This article demonstrates how to develop a TCP Echo Server with this API.
Java Network Server Programming One article demonstrates how to write a simple TCP Echo Server using Java's Socket API. Its blocking IO is simpler, but each client requires a separate thread to process, this approach is no longer possible when the server needs to handle a large number of clients at the same time. Use the NIO API to allow one or limited number of Thread simultaneously handle all clients connected to the server. (About some introduction to the NIO API, you can find it in the Java Nio API.)
NIO API allows a thread to monitor multiple SelectableChannel to handle multiple IO, NIO applications generally followed by the Selector object:
Figure 1
As shown in Figure 1, the client is looped in the SELECT operation. After select () returns, the SelectableChannel, which needs to be processed is obtained and processed one by one by selectedKeys ().
Although it is simple but there is a problem, when there is a different type of SelectableChannel, when doing different IO processing, the CLIENT code needs to judge the type of Channel and then make a corresponding operation, which often means a series of IF ELSE. . Worse, every additional new Channel does not only need to increase the corresponding processing code, but also need to maintain this string IF ELSE. (In this example of this article, we have two CHANNELs in Serversocketchannel and SocketChannel need to be processed.)
If you consider encapsulation of Channel and its IO processing, you can solve this problem. Nioseession in Listing 1 is this interface.
Niosession's channel () method returns its packaged SelectableChannel object, and InterestS () returns InterestOps for this Channel registration. Registered () is the callback function that is called after the SelectableChannel is registered, through this callback function, Niosession can get the SelectionKey after Channel registration. The Process () function is the core of the Niosession interface, which abstracts the IO processing logic required for the packaged SelectableChannel.
Listing 1:
Public interface nioseession {public selectablechannel channel (); public int interest (); public void key; public void process ();
And Niosession work together is a class (Listing 2), which is a NioseSession caller, encapsulated a selector object and logic of the cycle SELECT operation in Figure 1. Understanding this class can help us understand how to use the Nioseession interface.
Niworker implements the runnable interface, the logic of the cycle SELECT operation is in the RUN () method. In the framework of NiOWORKER - NioseSession, Niosession will be sent to the Register function as an attachment when Channel is registered, so that every SelectEdKeys () in SelectEdKeys () is passed in SelectEdKeys (). Attachment gets its corresponding nioseession and then calls its process () method. Each Select () loop has a task, that is, the Niosession that will be added to this Niworker through the add () method to register on the Selector. As can be seen in the code of Listing 2, the Channel () in the niosession is taken out and registered on the Selector, and the INTERESTOPS required for registration is removed from the Niosession. The Niosession itself is sent to the register () function as an Attachment. After the registration is successful, the Niosession's registered () callback function is called.
The role of Niworker's add () method is to add a Niosession to the niworker, and the current SELECT operation is in this next select () call, this Niosession will be registered. The Stop () method is to stop a Niworker in Run (). CloseallChannels () Close all Channel of the current registration, which can be used to release the IO resource when Niworker is no longer used.
Listing 2:
public class NioWorker implements Runnable {public NioWorker (Selector sel) {_sel = sel; _added = new HashSet ();} public void run () {try {try {while (_run) {_sel.select (); Set selected = _sel .selectedKeys (); for (Iterator ITR = SELECTED.ITERATOR (); itr.hasnext ();) {SelectionKey Key = (SelectionKey) iTr.next (); niosession s = (nioseession) key.attachment (); s. Process (); itr.remove ();} synchronized (_added) {for (Iterator ITR = _Added.iterator (); itr.hasnext ();) {niosession s = (nioseession) iTr.next (); SelectionKey Key = S.Channel (). register (_sel, s.interestOps (); s); s.registered (key); itr.re ();}}}} finally {_sel.close ();}} catch (}}) {throw new error (ex);}} public void add (niosession s) {synchronized (_ADDED) {_added.add ( s);} _sel.wakeup ();} public synchronized void stop () {_run = false; _sel.wakeup ();} public void closeallchannel () {for (Iterator ITR = _SEL.KEYS (). itemator (); iTr.hasnext ();) {SelectionKey Key = (SelectionKey) iTr.next (); try {key.channel (). Close ();
} Catch (IOException ex) {}}} protected Selector _sel = null; protected Collection _added = null; protected volatile boolean _run = true;} In Echo Server this example, we need a ServerSocketChannel to accept new TCP connections for each A TCP connection, we also need a socketchannel to handle the IO operation on this TCP connection. Together, these two Channel and the NiOWorker - Niosession structure are integrated, and two classes of NioserverSession and NioechoseSSION are encapsulated by ServersocketChannel and SocketChannel and their corresponding IO operations. The following UML class diagram describes the relationships of these 4 classes:
Figure 2
You can see that NiOOWorker and Niosession do not have any dependencies for newly added classes, NioserverSession and Nioechoseses have added new features to the system by implementing Niosession interface. Such a system architecture meets the Open-Close principle, and the new feature can be added to the original module by achieving NioseSession, which reflects the powerful power of object-oriented design.
NioserverSession (Listing 3) is relatively simple, which encapsulates a logic of ServersocketChannel and accepts new TCP connections from this Channel. NioserverSession also requires a Niworker reference, so every new TCP connection, NioserverSession creates a Nioechosession object for it and adds this object to niworker.
Listing 3:
public class NioServerSession implements NioSession {public NioServerSession (ServerSocketChannel channel, NioWorker worker) {_channel = channel; _worker = worker;} public void registered (SelectionKey key) {} public void process () {try {SocketChannel c = _channel.accept () ; If (c! = Null) {c.configureblocking (false); nioechosesis s = new nioechosesis (c); _Worker.Add (s);}} catch (ooException ex) {throw new error (ex);}} PUBLIC SelectableChannel channel () {return _channel;} public int interestOps () {return SelectionKey.OP_ACCEPT;} protected ServerSocketChannel _channel; protected NioWorker _worker;} NioEchoSession more complex behavior, NioEchoSession will start reading data in the TCP connection, and then These data are written back with the same connection and repeat this step until the client is turned off. We can regard "reading" and "Writing" as the two states of NioecHoseSession, which can be used to describe its behavior with a limited state machine, as shown below:
Figure 3
The next job is how to achieve this limited state machine. In this example, we use the State mode to implement it. The following UML class diagram describes the design details of the NIOECHOSession.
Figure 4
The status of the Nioechosession is manifested by the abstraction of Echostate, and the two subclasses correspond to "Reading" and "Writing". NioecHoseSession will delegrate DELEGATE for echostate to echostate, which will have different behaviors when NioechoseSession is in different states.
Listing 4 is the implementation of Echostate. Echostate defines two abstract ways of Process () and Interestops () to make subclass implementation. The process () method in the NIOECHOSSION will be DELEGATE to its current echostate's Process () method, and the NIOECHOSESSION itself will also be sent to the process () method of the Echostate as a parameter describing the context. Echostate defined Interestops () methods are used when NioecHosession registration and transitioning State.
Echostate also defines two static methods to return to the preclinted ReadState and WriteState, which can avoid creating some unnecessary objects when the NioecHoseSession converts State. However, this requires the State class must be stateless, the status needs to be saved in the Context class, which is the nioechosesis. Listing 4:
public abstract class EchoState {public abstract void process (NioEchoSession s) throws IOException; public abstract int interestOps (); public static EchoState readState () {return _read;} public static EchoState writeState () {return _write;} protected static EchoState _read = New readState (); protected static echostate_write = new writestate ();
Listing 5 is the implementation of NioecHosesis. NioecHoseSession contains a SocketChannel, which obtained SelectionKey after registration, a Bytebuffer for storing data and a Echostate object that records current State. At initialization, echostate is initialized to a ReadState. Nioechosession puts the process () method and the interestops () method DELEGATE to the current echostate. Its setState () method is used to switch the current state. After switching State, NioechoseSession will update the registered Interest on SelectionKey. Close () method is used to close this NIOECHOSESSION object.
Listing 5:
public class NioEchoSession implements NioSession {public NioEchoSession (SocketChannel c) {_channel = c; _buf = ByteBuffer.allocate (128); _state = EchoState.readState ();} public void registered (SelectionKey key) {_key = key;} public void Process () {try {_State.Process (this);} catch (ooException ex) {close (); throw new error (ex);}} public selectablechannel channel () {return_channel;} public int intertOps ()} _State.INTERESTOPS ();} public void setState (echostate state) {_State = state; _key.interest ());} public void close ()} (} catch (ooException ex)} catch (ooException ex) { Throw new error (ex);}} protected socketchannel _channel = null; protected selectionKey_key; protected bytebuffer _buf = nu LL; protected echostate _State = null;} Listing 6 and Listing 7 are ReadState and WriteState implementations, respectively. ReadState will read data from the Nioechosession Channel in Process (). If you fail to read the data, NioechoseSession will continue to stay in ReadState; if the error is read, NioechoseSession will be turned off; if the read is successful, NioecHosession will be handover Go to WriteState. WriteState is responsible for writing the data that has been read in the NIOECHOSSION back to CHANEL. After all are written, NioecHoseSession will switch back to ReadState.
Listing 6:
public class ReadState extends EchoState {public void process (NioEchoSession s) throws IOException {SocketChannel channel = s._channel; ByteBuffer buf = s._buf; int count = channel.read (buf); if (count == 0) {return; } If (count == -1); return;} buf.flip (); s.setstate (echostate.writestate ());} public int interest 2 ());} (});} Listing 7:
public class WriteState extends EchoState {public void process (NioEchoSession s) throws IOException {SocketChannel channel = s._channel; ByteBuffer buf = s._buf; channel.write (buf); if (buf.remaining () == 0) {buf .clear (); s.setState (echostate.readstate ());}} public int inteestops ()}}}}
Nioechoserver (Listing 8) is used to start and close a TCP Echo Server, which implements the runnable interface, and the Run () method is called to launch the Echo Server. Its shutdown () method is used to close this Echo Server, pay attention to the synchronization code in the Finally Block in ShutDown () and Run () ensures that the shutdown () method will return only if the Echo Server is turned off.
Listing 8:
public class NioEchoServer implements Runnable {public void run () {try {ServerSocketChannel serv = ServerSocketChannel.open (); try {serv.socket () bind (new InetSocketAddress (7));. serv.configureBlocking (false); _worker = new NiOSERVERSESSION S = New NioserverSession (serv, _worker); _Worker.Add (s); _Worker.Run ();} finally {_worker.closeallChannels (); synchronized (this) {notify () ;}}} Catch (ooException ex) {throw new error (ex);}} public synchronized void shutdown () {_Worker.stop (); try {wait ();} catch (interruptedException ex) {throw new error (EX) );}} Protected niworker _Worker = null;} Finally, through a simple main () function (Listing 9), we can run this Echo Server.
Listing 9:
Public static void main (String [] args) {new nioechoserver (). run ();
We can verify the health of this program through the Telnet program:
1. Open a command line, enter Telnet LocalHost 7 to run a Telnet program and connect to the Echo Server.
2. In the Telnet program, you can see the characters entered on the screen. (This is because Echo Server writes back to the client)
3. Open a few Telnet programs for testing, you can see that the Echo Server can use a Thread service with a Thread service through the Nio API.
Author: DaiJiaLin mailto: woodydai@gmail.comhttp: //blog.9cbs.net/DaiJiaLin