Tải bản đầy đủ - 0 (trang)
Example 4-1. Using select() to service multiple channels

Example 4-1. Using select() to service multiple channels

Tải bản đầy đủ - 0trang

*/

public class SelectSockets

{

public static int PORT_NUMBER = 1234;

public static void main (String [] argv)

throws Exception

{

new SelectSockets().go (argv);

}

public void go (String [] argv)

throws Exception

{

int port = PORT_NUMBER;

if (argv.length > 0) {

// Override default listen port

port = Integer.parseInt (argv [0]);

}

System.out.println ("Listening on port " + port);

// Allocate an unbound server socket channel

ServerSocketChannel serverChannel = ServerSocketChannel.open();

// Get the associated ServerSocket to bind it with

ServerSocket serverSocket = serverChannel.socket();

// Create a new Selector for use below

Selector selector = Selector.open();

// Set the port the server channel will listen to

serverSocket.bind (new InetSocketAddress (port));

// Set nonblocking mode for the listening socket

serverChannel.configureBlocking (false);

// Register the ServerSocketChannel with the Selector

serverChannel.register (selector, SelectionKey.OP_ACCEPT);

while (true) {

// This may block for a long time. Upon returning, the

// selected set contains keys of the ready channels.

int n = selector.select();

if (n == 0) {

continue;

}



// nothing to do



// Get an iterator over the set of selected keys

Iterator it = selector.selectedKeys().iterator();

// Look at each key in the selected set

while (it.hasNext()) {

SelectionKey key = (SelectionKey) it.next();

// Is a new connection coming in?

if (key.isAcceptable()) {

ServerSocketChannel server =



148



(ServerSocketChannel) key.channel();

SocketChannel channel = server.accept();

registerChannel (selector, channel,

SelectionKey.OP_READ);

sayHello (channel);

}

// Is there data to read on this channel?

if (key.isReadable()) {

readDataFromSocket (key);

}

// Remove key from selected set; it's been handled

it.remove();

}

}

}

// ---------------------------------------------------------/**

* Register the given channel with the given selector for

* the given operations of interest

*/

protected void registerChannel (Selector selector,

SelectableChannel channel, int ops)

throws Exception

{

if (channel == null) {

return;

// could happen

}

// Set the new channel nonblocking

channel.configureBlocking (false);

// Register it with the selector

channel.register (selector, ops);

}

// ---------------------------------------------------------// Use the same byte buffer for all channels. A single thread is

// servicing all the channels, so no danger of concurrent acccess.

private ByteBuffer buffer = ByteBuffer.allocateDirect (1024);

/**

* Sample data handler method for a channel with data ready to read.

* @param key A SelectionKey object associated with a channel

* determined by the selector to be ready for reading. If the

* channel returns an EOF condition, it is closed here, which

* automatically invalidates the associated key. The selector

* will then de-register the channel on the next select call.

*/

protected void readDataFromSocket (SelectionKey key)

throws Exception



149



{

SocketChannel socketChannel = (SocketChannel) key.channel();

int count;

buffer.clear();



// Empty buffer



// Loop while data is available; channel is nonblocking

while ((count = socketChannel.read (buffer)) > 0) {

buffer.flip();

// Make buffer readable

// Send the data; don't assume it goes all at once

while (buffer.hasRemaining()) {

socketChannel.write (buffer);

}

// WARNING: the above loop is evil. Because

// it's writing back to the same nonblocking

// channel it read the data from, this code can

// potentially spin in a busy loop. In real life

// you'd do something more useful than this.

buffer.clear();



// Empty buffer



}

if (count < 0) {

// Close channel on EOF, invalidates the key

socketChannel.close();

}

}

// ---------------------------------------------------------/**

* Spew a greeting to the incoming client connection.

* @param channel The newly connected SocketChannel to say hello to.

*/

private void sayHello (SocketChannel channel)

throws Exception

{

buffer.clear();

buffer.put ("Hi there!\r\n".getBytes());

buffer.flip();

channel.write (buffer);

}

}



Example 4-1 implements a simple server. It creates ServerSocketChannel and Selector

objects and registers the channel with the selector. We don't bother saving a reference to

the registration key for the server socket because it will never be deregistered. The

infinite loop calls select() at the top, which may block indefinitely. When selection is

complete, the selected key set is iterated to check for ready channels.

If a key indicates that its channel is ready to do an accept(), we obtain the channel

associated with the key and cast it to a ServerSocketChannel object. We know it's safe to

150



do this because only ServerSocketChannel objects support the OP_ACCEPT operation. We

also know our code registers only a single ServerSocketChannel object with interest in

OP_ACCEPT. With a reference to the server socket channel, we invoke accept() on it to

obtain a handle to the incoming socket. The object returned is of type SocketChannel,

which is also a selectable type of channel. At this point, rather than spawning a new

thread to read data from the new connection, we simply register the socket channel with

the selector. We tell the selector we're interested in knowing when the new socket

channel is ready for reading by passing in the OP_READ flag.

If the key did not indicate that the channel was ready for accept, we check to see if it's

ready for read. Any socket channels indicating so will be one of the SocketChannel

objects previously created by the ServerSocketChannel and registered for interest in

reading. For each socket channel with data to read, we invoke a common routine to read

and process the data socket. Note that this routine should be prepared to deal with

incomplete data on the socket, which is in nonblocking mode. It should return promptly

so that other channels with pending input can be serviced in a timely manner. Example

4-1 simply echoes the data back down the socket to the sender.

At the bottom of the loop, we remove the key from the selected key set by calling

remove() on the Iterator object. Keys can be removed directly from the Set returned by

selectedKeys(), but when examining the set with an Iterator, you should use the iterator's

remove() method to avoid corrupting the iterator's internal state.

4.3.4 Concurrency

Selector objects are thread-safe, but the key sets they contain are not. The key sets

returned by the keys() and selectedKeys() methods are direct references to private Set

objects inside the Selector object. These sets can change at any time. The registered key

set is read-only. If you attempt to modify it, your reward will be a

java.lang.UnsupportedOperationException, but you can still run into trouble if it's

changed while you're looking at it. Iterator objects are fail-fast: they will throw

java.util.ConcurrentModificationException if the underlying Set is modified, so be

prepared for this if you expect to share selectors and/or key sets among threads. You're

allowed to modify the selection key set directly, but be aware that you could clobber

some other thread's Iterator by doing so.

If there is any question of multiple threads accessing the key sets of a selector

concurrently, you must take steps to properly synchronize access. When performing a

selection operation, selectors synchronize on the Selector object, the registered key set,

and the selected key set objects, in that order. They also synchronize on the cancelled key

set during Steps 1 and 3 of the selection process (when it deregisters channels associated

with cancelled keys).

In a multithread scenario, if you need to make changes to any of the key sets, either

directly or as a side effect of another operation, you should first synchronize on the same

objects, in the same order. The locking order is vitally important. If competing threads do



151



not request the same locks in the same order, there is a potential for deadlock. If you are

certain that no other threads will be accessing the selector at the same time, then

synchronization is not necessary.

The close() method of Selector synchronizes in the same way as select(), so there is a

potential for blocking there. A thread calling close() will block until an in-progress

selection is complete or the thread doing the selection goes to sleep. In the latter case, the

selecting thread will awaken as soon as the closing thread acquires the locks and closes

the selector (see Section 4.3.2).



4.4 Asynchronous Closability

It's possible to close a channel or cancel a selection key at any time. Unless you take

steps to synchronize, the states of the keys and associated channels could change

unexpectedly. The presence of a key in a particular key set does not guarantee that the

key is still valid or that its associated channel is still open.

Closing channels should not be a time-consuming operation. The designers of NIO

specifically wanted to prevent the possibility of a thread closing a channel being blocked

in an indefinite wait if the channel is involved in a select operation. When a channel is

closed, its associated keys are cancelled. This does not directly affect an in-process

select(), but it does mean that a selection key that was valid when you called select()

could be invalid upon return. You should always use the selected key set returned by the

selector's selectedKeys() method; do not maintain your own set of keys. Understanding

the selection process as outlined in Section 4.3.1 is important to avoid running into

trouble.

Refer to Section 4.3.2 for the details of how a thread can be awakened when blocked in

select().

If you attempt to use a key that's been invalidated, a CancelledKeyException will be

thrown by most methods. You can, however, safely retrieve the channel handle from a

cancelled key. If the channel has also been closed, attempting to use it will yield a

ClosedChannelException in most cases.



4.5 Selection Scaling

I've mentioned several times that selectors make it easy for a single thread to multiplex

large numbers of selectable channels. Using one thread to service all the channels reduces

complexity and can potentially boost performance by eliminating the overhead of

managing many threads. But is it a good idea to use just one thread to service all

selectable channels? As always, it depends.

It could be argued that on a single CPU system it's a good idea because only one thread

can be running at a time anyway. By eliminating the overhead of context switching

between threads, total throughput could be higher. But what about a multi-CPU system?



152



On a system with n CPUs, n-1 could be idling while the single thread trundles along

servicing each channel sequentially.

Or what about the case in which different channels require different classes of service?

Suppose an application logs information from a large number of distributed sensors. Any

given sensor could wait several seconds while the servicing thread iterates through each

ready channel. This is OK if response time is not critical. But higher-priority connections

(such as operator commands) would have to wait in the queue as well if only one thread

services all channels. Every application's requirements are different. The solutions you

apply are affected by what you're trying to accomplish.

For the first scenario, in which you want to bring more threads into play to service

channels, resist the urge to use multiple selectors. Performing readiness selection on large

numbers of channels is not expensive; most of the work is done by the underlying

operating system. Maintaining multiple selectors and randomly assigning channels to one

of them is not a satisfactory solution to this problem. It simply makes smaller versions of

the same scenario.

A better approach is to use one selector for all selectable channels and delegate the

servicing of ready channels to other threads. You have a single point to monitor channel

readiness and a decoupled pool of worker threads to handle the incoming data. The thread

pool size can be tuned (or tune itself, dynamically) according to deployment conditions.

Management of selectable channels remains simple, and simple is good.

The second scenario, in which some channels demand greater responsiveness than others,

can be addressed by using two selectors: one for the command connections and another

for the normal connections. But this scenario can be easily addressed in much the same

way as the first. Rather than dispatching all ready channels to the same thread pool,

channels can be handed off to different classes of worker threads according to function.

There may be a logging thread pool, a command/control pool, a status request pool, etc.

The code in Example 4-2 is an extension of the generic selection loop code in Example

4-1. It overrides the readDataFromSocket() method and uses a thread pool to service

channels with data to read. Rather than reading the data synchronously in the main thread,

this version passes the SelectionKey object to a worker thread for servicing.

Example 4-2. Servicing channels with a thread pool

package com.ronsoft.books.nio.channels;

import

import

import

import

import

import



java.nio.ByteBuffer;

java.nio.channels.SocketChannel;

java.nio.channels.SelectionKey;

java.util.List;

java.util.LinkedList;

java.io.IOException;



/**



153



* Specialization of the SelectSockets class which uses a thread pool

* to service channels. The thread pool is an ad-hoc implementation

* quicky lashed togther in a few hours for demonstration purposes.

* It's definitely not production quality.

*

* @author Ron Hitchens (ron@ronsoft.com)

*/

public class SelectSocketsThreadPool extends SelectSockets

{

private static final int MAX_THREADS = 5;

private ThreadPool pool = new ThreadPool (MAX_THREADS);

// ------------------------------------------------------------public static void main (String [] argv)

throws Exception

{

new SelectSocketsThreadPool().go (argv);

}

// ------------------------------------------------------------/**

* Sample data handler method for a channel with data ready to read.

* This method is invoked from the go() method in the parent class.

* This handler delegates to a worker thread in a thread pool to

* service the channel, then returns immediately.

* @param key A SelectionKey object representing a channel

* determined by the selector to be ready for reading. If the

* channel returns an EOF condition, it is closed here, which

* automatically invalidates the associated key. The selector

* will then de-register the channel on the next select call.

*/

protected void readDataFromSocket (SelectionKey key)

throws Exception

{

WorkerThread worker = pool.getWorker();

if (worker == null) {

// No threads available. Do nothing. The selection

// loop will keep calling this method until a

// thread becomes available. This design could

// be improved.

return;

}

// Invoking this wakes up the worker thread, then returns

worker.serviceChannel (key);

}

// --------------------------------------------------------------/**

* A very simple thread pool class. The pool size is set at

* construction time and remains fixed. Threads are cycled

* through a FIFO idle queue.



154



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Example 4-1. Using select() to service multiple channels

Tải bản đầy đủ ngay(0 tr)

×