JAVA NIO的实例
1:JAVA NIO简介
Java NIO非堵塞技术实际是采取Reactor模式,或者说是Observer模式为我们监察I/O端口,假如有内容进来,会自动通知我们,这样,我们就不必开启多个线程死等,从外界看,实现了流畅的I/O读写,不堵塞了。NIO 有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情,当有事件发生时,他会通知我们,传回一组SelectionKey,我们读取这些Key,就会获得我们刚刚注册过的socketchannel,然后,我们从这个Channel中读取数据,放心,包准能够读到,接着我们可以处理这些数据。Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容。
2: 服务器端:SumServer.java类
package nio; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; import java.util.Iterator; import java.util.Set; /** * SumServer.java * * * Created: Thu Nov 06 11:41:52 2003 * * @author starchu1981 * @version 1.0 */ public class SumServer { private ByteBuffer _buffer = ByteBuffer.allocate(8); private IntBuffer _intBuffer = _buffer.asIntBuffer(); private SocketChannel _clientChannel = null; private ServerSocketChannel _serverChannel = null; public void start() { try { openChannel(); waitForConnection(); } catch (IOException e) { System.err.println(e.toString()); } } private void openChannel() throws IOException { _serverChannel = ServerSocketChannel.open(); _serverChannel.socket().bind(new InetSocketAddress(10001)); _serverChannel.configureBlocking(false);// 设置为非阻塞形式 System.out.println("服务器通道已经打开"); } /* * private void waitForConnection() throws IOException { while (true) { * _clientChannel = _serverChannel.accept(); if (_clientChannel != null) { * System.out.println("新的连接加入"); processRequest(); _clientChannel.close(); } } } */ private void waitForConnection() throws IOException { Selector acceptSelector = SelectorProvider.provider().openSelector(); /* * 在服务器套接字上注册selector并设置为接受accept方法的通知。 * 这就告诉Selector,套接字想要在accept操作发生时被放在ready表 上,因此,允许多元非阻塞I/O发生。 */ SelectionKey acceptKey = _serverChannel.register(acceptSelector, SelectionKey.OP_ACCEPT); int keysAdded = 0; /* select方法在任何上面注册了的操作发生时返回 */ while ((keysAdded = acceptSelector.select()) > 0) { // 某客户已经准备好可以进行I/O操作了,获取其ready键集合 Set readyKeys = acceptSelector.selectedKeys(); Iterator i = readyKeys.iterator(); // 遍历ready键集合,并处理加法请求 while (i.hasNext()) { SelectionKey sk = (SelectionKey) i.next(); i.remove(); if (sk.isAcceptable()) { ServerSocketChannel nextReady = (ServerSocketChannel) sk .channel(); // 接受加法请求并处理它 _clientChannel = nextReady.accept(); // Socket _clientSocket = _clientChannel.socket(); processRequest(); // _clientSocket.close(); } } } } private void processRequest() throws IOException { _buffer.clear(); _clientChannel.read(_buffer); int result = _intBuffer.get(0) + _intBuffer.get(1); _buffer.flip(); _buffer.clear(); _intBuffer.put(0, result); _clientChannel.write(_buffer); } public static void main(String[] args) { new SumServer().start(); } } // SumServer
3:客户端:SumClient.java类
package nio; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.channels.SocketChannel; import java.net.InetSocketAddress; import java.io.IOException; /** * SumClient.java * * * Created: Thu Nov 06 11:26:06 2003 * * @author starchu1981 * @version 1.0 */ public class SumClient { private ByteBuffer _buffer = ByteBuffer.allocate(8); private IntBuffer _intBuffer; private SocketChannel _channel; public SumClient() { _intBuffer = _buffer.asIntBuffer(); } // SumClient constructor public int getSum(int first, int second) { int result = 0; try { _channel = connect(); sendSumRequest(first, second); result = receiveResponse(); } catch (IOException e) { System.err.println(e.toString()); } finally { if (_channel != null) { try { _channel.close(); } catch (IOException e) { } } } return result; } private SocketChannel connect() throws IOException { InetSocketAddress socketAddress = new InetSocketAddress("localhost", 10001); return SocketChannel.open(socketAddress); } private void sendSumRequest(int first, int second) throws IOException { _buffer.clear(); _intBuffer.put(0, first); _intBuffer.put(1, second); _channel.write(_buffer); System.out.println("发送加法请求 " + first + "+" + second); } private int receiveResponse() throws IOException { _buffer.clear(); _channel.read(_buffer); return _intBuffer.get(0); } public static void main(String[] args) { SumClient sumClient = new SumClient(); System.out.println("加法结果为 :" + sumClient.getSum(100, 324)); } } // SumClient
JAVA NIO MappedByteBuffer共享内存
Java NIO 应用 — 使用内存映射文件实现进程间通信
March 19, 2010 — Unmi
一看到 Java NIO 的内存映射文件(MappedByteBuffer),让我立即就联想到 Windows 系统的内存映射文件。Windows 系统的内存映射文件能用来在多个进程间共享数据,即进程间的共享内存,是通过把同一块内存区域映射到不同进程的地址空间中,从而达到共享内存。
Java NIO 的内存映射文件和 Windows 系统下的一样,都能把物理文件的内容映射到内存中,那么 MappedByteBuffer 是否能用来在不同 Java 进程(JVM) 间共享数据呢?答案是肯定的,这样在通常的 Socket 方式来实现 Java 进程间通信之上又多了一种方法。
在 Windows 中内存映射文件可以是脱离物理文件而存在的一块命名的内存区域,使用相同的内存映射名就能在不同的进程中共享同一片内存。然后,Java 的 MappedByteBuffer 总是与某个物理文件相关的,因为不管你是从 FileInputStream、FileOutputStream 还是 RandomAccessFile 得来的 FileChannel,再 map() 得到的内存映射文件 MappedByteBuffer,如果在构造 FileInputStream、FileOutputStream、RandomAccessFile 对象时不指定物理文件便会有 FileNotFoundException 异常。
所以 Java NIO 来实现共享内存的办法就是让不同进程的内存映射文件关联到同一个物理文件,因为 MappedByteBuffer 能让内存与文件即时的同步内容。严格说来,称之为内存共享是不准确的,其实就是两个 Java 进程通过中间文件来交换数据,用中间文件使得两个进程的两块内存区域的内容得到及时的同步。
用图来理解 Java NIO 的“共享内存”的实现原理:
知道了实现原理之后,下面用代码来演示两个进程间用内存映射文件来进行数据通信。代码 WriteShareMemory.java 往映射文件中依次写入 A、B、C … Z,ReadShareMemory.java 逐个读出来,打印到屏幕上。代码对交换文件 swap.mm 的第一个字节作了读写标志,分别是 0-可读,1-正在写,2-可读。RandomAccessFile 得到的 Channel 能够灵活的进行读或写,并且不会破坏原有文件内容,而 FileInputStream 或 FileOutputStream 取得的 Channel 则很难达到这一功效,所以使用了 RandomAccessFile 来获得 FileChannel。
WriteShareMemory.java
03 |
import java.io.RandomAccessFile; |
04 |
import java.nio.MappedByteBuffer; |
05 |
import java.nio.channels.FileChannel; |
06 |
import java.nio.channels.FileChannel.MapMode; |
12 |
public class WriteShareMemory { |
18 |
public static void main(String[] args) throws Exception { |
19 |
RandomAccessFile raf = new RandomAccessFile( "c:/swap.mm" , "rw" ); |
20 |
FileChannel fc = raf.getChannel(); |
21 |
MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0 , 1024 ); |
24 |
for ( int i= 0 ;i< 1024 ;i++){ |
25 |
mbb.put(i,( byte ) 0 ); |
29 |
for ( int i= 65 ;i< 91 ;i++){ |
31 |
int flag = mbb.get( 0 ); |
36 |
mbb.put( 0 ,( byte ) 1 ); |
37 |
mbb.put( 1 ,( byte )(index)); |
39 |
System.out.println( "程序 WriteShareMemory:" +System.currentTimeMillis() + |
40 |
":位置:" + index + " 写入数据:" + ( char )i); |
42 |
mbb.put(index,( byte )i); |
43 |
mbb.put( 0 ,( byte ) 2 ); |
ReadShareMemory.java
03 |
import java.io.RandomAccessFile; |
04 |
import java.nio.MappedByteBuffer; |
05 |
import java.nio.channels.FileChannel; |
06 |
import java.nio.channels.FileChannel.MapMode; |
12 |
public class ReadShareMemory { |
18 |
public static void main(String[] args) throws Exception { |
19 |
RandomAccessFile raf = new RandomAccessFile( "c:/swap.mm" , "rw" ); |
20 |
FileChannel fc = raf.getChannel(); |
21 |
MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0 , 1024 ); |
24 |
for ( int i= 1 ;i< 27 ;i++){ |
25 |
int flag = mbb.get( 0 ); |
26 |
int index = mbb.get( 1 ); |
28 |
if (flag != 2 || index == lastIndex){ |
34 |
System.out.println( "程序 ReadShareMemory:" + System.currentTimeMillis() + |
35 |
":位置:" + index + " 读出数据:" + ( char )mbb.get(index)); |
37 |
mbb.put( 0 ,( byte ) 0 ); |
在 Eclipse 中运行 WriteShareMemory,然后到命令行下运行 ReadShareMemory,你将会看到 WriteShareMemory 写一个字符,ReadShareMemory 读一个。
代码中使用了读写标志位,和写入的索引位置,所以在 WriteShareMemory 写入一个字符后,只有等待 ReadShareMemory 读出刚写入的字符后才会写入第二个字符。实际应用中可以加入更好的通知方式,如文件锁等。
你也可以查看执行时 c:/swap.mm 文件的内容来验证这一过程,因为 MappedByteBuffer 在运行时是一种 DirectByteBuffer,所以它能与文件即时的同步内容,无须通过 FileChannel 来 write(buffer) 往文件中手工写入数据,或 read(buffer) 手工读数据到内存中。