Summary:::
---------------------------------------------------------------------------------
Using multiple threads well is very important in any Java program that performs a lot of I/O. In the simplest case, I/O (and particularly socket I/O) may block at any point in time; if you want to make sure that your program remains responsive while performing I/O, you must perform the I/O in another thread. For simple cases, this means having a single thread for every I/O source you're interested in.
That model does not scale completely as the number of I/O sources grows. At this point, you must begin to look at other threading solutions. One solution is to continue to use blocking I/O but to limit the number of threads active at any time. Although that solution has limited applicability, it's a simple extension to a basic idea.
In most other cases, you'll need to use the nonblocking features of Java's NIO classes. Although these classes increase the complexity of your applications, they allow you to handle many I/O sources with a single thread. The complexity of using nonblocking I/O can be mitigated somewhat by using multiple threads with nonblocking I/O; that solution is also appropriate when you have multiple CPUs available to process requests or when the requests themselves need to block for other reasons.
1,Thraditional I/O Server
2,New I/O Server
3,Interrupted I/O
1,使用传统I/O的解决是采用多线程。一个Thread对应一个I/O。Leader-Follows模式,一个分发I/O任务(ServerSocket.accpet();),n个线程进行blocking I/O。
好处:
a,容易编程,模型简单
问题:
a,给每个客户端建立一个连接线程十分耗费内存,尤其是CPU,特别是处理上千的客户连接的时候。上千的线程运行起来十分笨重。(可以通过有限的线程池来解决)
Continually making new connections to the server can be a nuisance, as well as having performance implications: it takes a significant amount of time to set up a socket connection. If the protocol of your application is such that messages flow frequently between client and server, this implementation is inefficient. For applications that handle a large number of clients making single requests, however, this is a good way to scale your server using traditional I/O.
结论:
The traditional I/O server cannot scale up to thousands of clients, and the traditional throttled I/O server is suitable only for short-lived requests.
NIO Server
1,只用一个线程来接受所有的客户端socket。
This obviates the need for a single thread for every I/O socket (or file); instead, you can have a single thread that processes all client sockets.
That thread can check to see which sockets have data available, process that data, and then check again for data on all sockets. Depending on the operations the server has to perform, it may need (or want) to spawn some additional threads to assist with this processing, but the new I/O classes allow you to handle thousands of clients in a single thread.
NIO 和IO的区别:
The difference between blocking and nonblocking I/O is in how this situation is handled. With blocking I/O, the readUTF() method can just request the additional data. Requesting that data blocks until the data finally makes its way to the machine, at which point the readUTF() method can complete its construction of the string and return that string to the user.
With nonblocking I/O, that solution doesn't work. When a method attempts to read data and none is available, the method immediately returns with an indication that no data was present. You can't immediately retry reading the data because it still may not be available, and you'd end up continually wasting CPU cycles as you attempt to read the nonexistent data. Worse, you'd lose any benefit of nonblocking I/O: if you're going to read data until everything is ready, you may as well use traditional, blocking I/O.
When you use nonblocking I/O, then, it's your responsibility to be prepared for this situation and cope with the fact that all the data you need to process may not be immediately available. It's this programming that makes nonblocking I/O more difficult to use.
使得编程模型更加复杂,需要处理更多情况。
package javathreads.examples.ch12;import java.net.*;import java.io.*;import java.nio.channels.*;import java.util.*;public abstract class TCPNIOServer implements Runnable { protected ServerSocketChannel channel = null; private boolean done = false; protected Selector selector; protected int port = 8000; public void startServer( ) throws IOException { channel = ServerSocketChannel.open( ); channel.configureBlocking(false); ServerSocket server = channel.socket( ); server.bind(new InetSocketAddress(port)); selector = Selector.open( ); channel.register(selector, SelectionKey.OP_ACCEPT); } public synchronized void stopServer( ) throws IOException { done = true; channel.close( ); } protected synchronized boolean getDone( ) { return done; } public void run( ) { try { startServer( ); } catch (IOException ioe) { System.out.println("Can't start server: " + ioe); return; } while (!getDone( )) { try { selector.select( ); } catch (IOException ioe) { System.err.println("Server error: " + ioe); return; } Iterator it = selector.selectedKeys( ).iterator( ); while (it.hasNext( )) { SelectionKey key = (SelectionKey) it.next( ); if (key.isReadable( ) || key.isWritable( )) { // Key represents a socket client try { handleClient(key); } catch (IOException ioe) { // Client disconnected key.cancel( ); } } else if (key.isAcceptable( )) { try { handleServer(key); } catch (IOException ioe) { // Accept error; treat as fatal throw new IllegalStateException(ioe); } } else System.out.println("unknown key state"); it.remove( ); } } } protected void handleServer(SelectionKey key) throws IOException { SocketChannel sc = channel.accept( ); sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); registeredClient(sc); } protected abstract void handleClient(SelectionKey key) throws IOException; protected abstract void registeredClient(SocketChannel sc) throws IOException;}
Our intent here is not to explain in great detail the NIO classes themselves; for a good reference, see Java NIO (O'Reilly). From a threading perspective, this is the classic approach to a single-threaded server than handles multiple clients. The selector keeps track of two things: the rendezvous socket and all open client sockets. When any of those sockets have data available, the selector is notified, and the set of sockets with pending data is returned via the selectedKeys() method. Our server iterates over each socket in that set. If the socket is the rendezvous socket, the handleServer() method is called, a new client connection is made, and the client socket is registered with the selector. Otherwise, the socket is a client data socket, and the handleClient() method is called.
The reason we can do this all in a single thread is that the I/O that occurs in the handleClient() and handleServer() methods never blocks. Consequently, our single thread never blocks; even with thousands of client sockets with pending I/O, each is handled in turn.
Trackback: http://tb.donews.net/TrackBack.aspx?PostId=445769