NIO是Java提供的非阻塞I/O API.
非阻塞的意义在于可以使用一个线程对大量的数据连接进行处理,非常适用于"短数据长连接"的应用场景,例如即时通讯软件.
在一个阻塞C/S系统中,服务器要为每一个客户连接开启一个线程阻塞等待客户端发送的消息.若使用非阻塞技术,服务器可以使用一个线程对连接进行轮 询,无须阻塞等待.这大大减少了内存资源的浪费,也避免了服务器在客户线程中不断切换带来的CPU消耗,服务器对CPU的有效使用率大大提高.
其核心概念包括Channel,Selector,SelectionKey,Buffer.
Channel是I/O通道,可以向其注册Selector,应用成功可以通过select操作获取当前通道已经准备好的可以无阻塞执行的操作.这由SelectionKey表示.
SelectionKey的常量字段SelectionKey.OP_***分别对应Channel的几种操作例如connect(),accept(),read(),write().
select操作后得到SelectionKey.OP_WRITE或者READ即可在Channel上面无阻塞调用read和write方 法,Channel的读写操作均需要通过Buffer进行.即读是讲数据从通道中读入Buffer然后做进一步处理.写需要先将数据写入Buffer然后 通道接收Buffer.
下面是一个使用NIO的基本C/S示例.该示例只为显示如何使用基本的API而存在,其代码的健壮性,合理性都不具参考价值.
这个示例,实现一个简单的C/S,客户端想服务器端发送消息,服务器将收到的消息打印到控制台.现实的应用中需要定义发送数据使用的协议,以帮助服 务器解析消息.本示例只是无差别的使用默认编码将收到的字节转换字符并打印.通过改变初始分配的ByteBuffer的容量,可以看到打印消息的变化.容 量越小,对一条消息的处理次数就越多,容量大就可以在更少的循环次数内读完整个消息.所以真是的应用场景,要考虑适当的缓存大小以提高效率.
首先是Server
01 |
package hadix.demo.nio; |
02 |
03 |
import java.io.IOException; |
04 |
import java.net.InetSocketAddress; |
05 |
import java.nio.ByteBuffer; |
06 |
import java.nio.channels.SelectionKey; |
07 |
import java.nio.channels.Selector; |
08 |
import java.nio.channels.ServerSocketChannel; |
09 |
import java.nio.channels.SocketChannel; |
10 |
import java.util.*; |
11 |
import java.util.concurrent.ConcurrentHashMap; |
12 |
13 |
/** |
14 |
* User: hAdIx |
15 |
* Date: 11-11-2 |
16 |
* Time: 上午11:26 |
17 |
*/ |
18 |
public class Server { |
19 |
private Selector selector; |
20 |
private ByteBuffer readBuffer = ByteBuffer.allocate( 8 ); //调整缓存的大小可以看到打印输出的变化 |
21 |
private Map<SocketChannel, byte []> clientMessage = new ConcurrentHashMap<>(); |
22 |
23 |
public void start() throws IOException { |
24 |
ServerSocketChannel ssc = ServerSocketChannel.open(); |
25 |
ssc.configureBlocking( false ); |
26 |
ssc.bind( new InetSocketAddress( "localhost" , 8001 )); |
27 |
selector = Selector.open(); |
28 |
ssc.register(selector, SelectionKey.OP_ACCEPT); |
29 |
while (!Thread.currentThread().isInterrupted()) { |
30 |
selector.select(); |
31 |
Set<SelectionKey> keys = selector.selectedKeys(); |
32 |
Iterator<SelectionKey> keyIterator = keys.iterator(); |
33 |
while (keyIterator.hasNext()) { |
34 |
SelectionKey key = keyIterator.next(); |
35 |
if (!key.isValid()) { |
36 |
continue ; |
37 |
} |
38 |
if (key.isAcceptable()) { |
39 |
accept(key); |
40 |
} else if (key.isReadable()) { |
41 |
read(key); |
42 |
} |
43 |
keyIterator.remove(); |
44 |
} |
45 |
} |
46 |
} |
47 |
48 |
private void read(SelectionKey key) throws IOException { |
49 |
SocketChannel socketChannel = (SocketChannel) key.channel(); |
50 |
51 |
// Clear out our read buffer so it's ready for new data |
52 |
this .readBuffer.clear(); |
53 |
54 |
// Attempt to read off the channel |
55 |
int numRead; |
56 |
try { |
57 |
numRead = socketChannel.read( this .readBuffer); |
58 |
} catch (IOException e) { |
59 |
// The remote forcibly closed the connection, cancel |
60 |
// the selection key and close the channel. |
61 |
key.cancel(); |
62 |
socketChannel.close(); |
63 |
clientMessage.remove(socketChannel); |
64 |
return ; |
65 |
} |
66 |
67 |
byte [] bytes = clientMessage.get(socketChannel); |
68 |
if (bytes == null ) { |
69 |
bytes = new byte [ 0 ]; |
70 |
} |
71 |
if (numRead > 0 ) { |
72 |
byte [] newBytes = new byte [bytes.length + numRead]; |
73 |
System.arraycopy(bytes, 0 , newBytes, 0 , bytes.length); |
74 |
System.arraycopy(readBuffer.array(), 0 , newBytes, bytes.length, numRead); |
75 |
clientMessage.put(socketChannel, newBytes); |
76 |
System.out.println( new String(newBytes)); |
77 |
} else { |
78 |
String message = new String(bytes); |
79 |
System.out.println(message); |
80 |
} |
81 |
} |
82 |
83 |
private void accept(SelectionKey key) throws IOException { |
84 |
ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); |
85 |
SocketChannel clientChannel = ssc.accept(); |
86 |
clientChannel.configureBlocking( false ); |
87 |
clientChannel.register(selector, SelectionKey.OP_READ); |
88 |
System.out.println( "a new client connected" ); |
89 |
} |
90 |
91 |
92 |
public static void main(String[] args) throws IOException { |
93 |
System.out.println( "server started..." ); |
94 |
new Server().start(); |
95 |
} |
96 |
} |
01 |
package hadix.demo.nio; |
02 |
03 |
import java.io.IOException; |
04 |
import java.net.InetSocketAddress; |
05 |
import java.nio.ByteBuffer; |
06 |
import java.nio.channels.SelectionKey; |
07 |
import java.nio.channels.Selector; |
08 |
import java.nio.channels.SocketChannel; |
09 |
import java.util.Iterator; |
10 |
import java.util.Scanner; |
11 |
import java.util.Set; |
12 |
13 |
/** |
14 |
* User: hAdIx |
15 |
* Date: 11-11-2 |
16 |
* Time: 上午11:26 |
17 |
*/ |
18 |
public class Client { |
19 |
20 |
public void start() throws IOException { |
21 |
SocketChannel sc = SocketChannel.open(); |
22 |
sc.configureBlocking( false ); |
23 |
sc.connect( new InetSocketAddress( "localhost" , 8001 )); |
24 |
Selector selector = Selector.open(); |
25 |
sc.register(selector, SelectionKey.OP_CONNECT); |
26 |
Scanner scanner = new Scanner(System.in); |
27 |
while ( true ) { |
28 |
selector.select(); |
29 |
Set<SelectionKey> keys = selector.selectedKeys(); |
30 |
System.out.println( "keys=" + keys.size()); |
31 |
Iterator<SelectionKey> keyIterator = keys.iterator(); |
32 |
while (keyIterator.hasNext()) { |
33 |
SelectionKey key = keyIterator.next(); |
34 |
keyIterator.remove(); |
35 |
if (key.isConnectable()) { |
36 |
sc.finishConnect(); |
37 |
sc.register(selector, SelectionKey.OP_WRITE); |
38 |
System.out.println( "server connected..." ); |
39 |
break ; |
40 |
} else if (key.isWritable()) { |
41 |
42 |
System.out.println( "please input message" ); |
43 |
String message = scanner.nextLine(); |
44 |
ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes()); |
45 |
sc.write(writeBuffer); |
46 |
} |
47 |
} |
48 |
} |
49 |
} |
50 |
51 |
public static void main(String[] args) throws IOException { |
52 |
new Client().start(); |
53 |
} |
54 |
} |
此外有一个代码写得更好的例子,非常值得参考.http://rox-xmlrpc.sourceforge.net/niotut/index.html
这个例子里面的客户端将消息发送给服务器,服务器收到后立即写回给客户端.例子中代码虽然也没有做有意义的处理,但是其结构比较合理,值得以此为基础进行现实应用的扩展开发.