java NIO

在JAVA中使用NIO进行网络编程

在JDK中,有一个非常有意思的库:NIO(New I/O)。这个库中有3个重要的类,分别是java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

本篇文章我们首先了解一下为什么需要NIO来进行网络编程,然后看看一步一步来讲解如何在网络编程中使用NIO。

为什么需要NIO

使用Java编写过Socket程序的同学一定都知道Socket和SocketServer。当调用某个调用的时候,调用的地方就会阻塞,等待响应。这种方式对于小规模的程序非常方便,但是对于大型的程序就有点力不从心了,当有大量的连接的时候,我们可以为每一个连接建立一个线程来操作。但是这种做法带来的缺陷也是显而易见的:

    1. 硬件能够支持大量的并发。

    2. 并发的数量始终有一个上限。

    3. 各个线程之间的优先级不好控制。

    4. 各个Client之间的交互与同步困难。

      我们也可以使用一个线程来处理所有的请求,使用不阻塞的IO,轮询查询所有的Client。这种做法同样也有缺陷:无法迅速响应Client端,同时会消耗大量轮询查询的时间。

      所以,我们需要一种poll的模式来处理这种情况,从大量的网络连接中找出来真正需要服务的Client。这正是NIO诞生的原因:提供一种Poll的模式,在所有的Client中找到需要服务的Client。

      回到我们刚刚说到的3个最最重要的Class:java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

      Channel代表一个可以被用于Poll操作的对象(可以是文件流也可以使网络流),Channel能够被注册到一个Selector中。通过调用Selector的select方法可以从所有的Channel中找到需要服务的实例(Accept,read ..)。Buffer对象提供读写数据的缓存。相对于我们熟悉的Stream对象,Buffer提供更好的性能以及更好的编程透明性(人为控制缓存的大小以及具体的操作)。

      配合BUFFER使用CHANNEL

      与传统模式的编程不用,Channel不使用Stream,而是Buffer。我们来实现一个简单的非阻塞Echo Client:

      package com.cnblogs.gpcuster;
      
      import java.net.InetSocketAddress;
      import java.net.SocketException;
      import java.nio.ByteBuffer;
      import java.nio.channels.SocketChannel;
      
      public class TCPEchoClientNonblocking {
      	public static void main(String args[]) throws Exception {
      		if ((args.length < 2) || (args.length > 3))// Testforcorrect#ofargs
      			throw new IllegalArgumentException(
      					"Parameter(s): <Server> <Word> [<Port>]");
      		String server = args[0];// ServernameorIPaddress
      		// ConvertinputStringtobytesusingthedefaultcharset
      		byte[] argument = args[1].getBytes();
      		int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7;
      		// Createchannelandsettononblocking
      		SocketChannel clntChan = SocketChannel.open();
      		clntChan.configureBlocking(false);
      		// Initiateconnectiontoserverandrepeatedlypolluntilcomplete
      		if (!clntChan.connect(new InetSocketAddress(server, servPort))) {
      			while (!clntChan.finishConnect()) {
      				System.out.print(".");// Dosomethingelse
      			}
      		}
      		ByteBuffer writeBuf = ByteBuffer.wrap(argument);
      		ByteBuffer readBuf = ByteBuffer.allocate(argument.length);
      		int totalBytesRcvd = 0;// Totalbytesreceivedsofar
      		int bytesRcvd;// Bytesreceivedinlastread
      		while (totalBytesRcvd < argument.length) {
      			if (writeBuf.hasRemaining()) {
      				clntChan.write(writeBuf);
      			}
      			if ((bytesRcvd = clntChan.read(readBuf)) == -1) {
      				throw new SocketException("Connection closed prematurely");
      			}
      			totalBytesRcvd += bytesRcvd;
      			System.out.print(".");// Dosomethingelse
      		}
      		System.out.println("Received:" + // converttoStringperdefaultcharset
      				new String(readBuf.array(), 0, totalBytesRcvd));
      		clntChan.close();
      	}
      }

      这段代码使用ByteBuffer来保存读写的数据。通过clntChan.configureBlocking(false); 设置后,其中的connect,read,write操作都不回阻塞,而是立刻放回结果。

      使用SELECTOR

       

       

      Selector的可以从所有的被注册到自己Channel中找到需要服务的实例。

      我们来实现Echo Server。

      首先,定义一个接口:

      package com.cnblogs.gpcuster;
      
      import java.nio.channels.SelectionKey;
      import java.io.IOException;
      
      public interface TCPProtocol {
      	void handleAccept(SelectionKey key) throws IOException;
      
      	void handleRead(SelectionKey key) throws IOException;
      
      	void handleWrite(SelectionKey key) throws IOException;
      }
      我们的Echo Server将使用这个接口。然后我们实现Echo Server:
      import java.io.IOException;
      import java.net.InetSocketAddress;
      import java.nio.channels.SelectionKey;
      import java.nio.channels.Selector;
      import java.nio.channels.ServerSocketChannel;
      import java.util.Iterator;
      
      public class TCPServerSelector {
      	private static final int BUFSIZE = 256;// Buffersize(bytes)
      	private static final int TIMEOUT = 3000;// Waittimeout(milliseconds)
      
      	public static void main(String[] args) throws IOException {
      		if (args.length < 1) {// Testforcorrect#ofargs
      			throw new IllegalArgumentException("Parameter(s):<Port>...");
      		}
      		// Createaselectortomultiplexlisteningsocketsandconnections
      		Selector selector = Selector.open();
      		// Createlisteningsocketchannelforeachportandregisterselector
      		for (String arg : args) {
      			ServerSocketChannel listnChannel = ServerSocketChannel.open();
      			listnChannel.socket().bind(
      					new InetSocketAddress(Integer.parseInt(arg)));
      			listnChannel.configureBlocking(false);// mustbenonblockingtoregister
      			// Registerselectorwithchannel.Thereturnedkeyisignored
      			listnChannel.register(selector, SelectionKey.OP_ACCEPT);
      		}
      		// Createahandlerthatwillimplementtheprotocol
      		TCPProtocol protocol = new EchoSelectorProtocol(BUFSIZE);
      		while (true) {// Runforever,processingavailableI/Ooperations
      		// Waitforsomechanneltobeready(ortimeout)
      			if (selector.select(TIMEOUT) == 0) {// returns#ofreadychans
      				System.out.print(".");
      				continue;
      			}
      			// GetiteratoronsetofkeyswithI/Otoprocess
      			Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
      			while (keyIter.hasNext()) {
      				SelectionKey key = keyIter.next();// Keyisbitmask
      				// Serversocketchannelhaspendingconnectionrequests?
      				if (key.isAcceptable()) {
      					protocol.handleAccept(key);
      				}
      				// Clientsocketchannelhaspendingdata?
      				if (key.isReadable()) {
      					protocol.handleRead(key);
      				}
      				// Clientsocketchannelisavailableforwritingand
      				// keyisvalid(i.e.,channelnotclosed)?
      				if (key.isValid() && key.isWritable()) {
      					protocol.handleWrite(key);
      				}
      				keyIter.remove();// removefromsetofselectedkeys
      			}
      		}
      	}
      }
       

      我们通过listnChannel.register(selector, SelectionKey.OP_ACCEPT); 注册了一个我们感兴趣的事件,然后调用selector.select(TIMEOUT)等待订阅的时间发生,然后再采取相应的处理措施。

      最后我们实现EchoSelectorProtocol

      package com.cnblogs.gpcuster;
      
      import java.nio.channels.SelectionKey;
      import java.nio.channels.SocketChannel;
      import java.nio.channels.ServerSocketChannel;
      import java.nio.ByteBuffer;
      import java.io.IOException;
      
      public class EchoSelectorProtocol implements TCPProtocol {
      	private int bufSize;// SizeofI/Obuffer
      
      	public EchoSelectorProtocol(int bufSize) {
      		this.bufSize = bufSize;
      	}
      
      	public void handleAccept(SelectionKey key) throws IOException {
      		SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
      		clntChan.configureBlocking(false);// Mustbenonblockingtoregister
      		// Registertheselectorwithnewchannelforreadandattachbytebuffer
      		clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer
      				.allocate(bufSize));
      	}
      
      	public void handleRead(SelectionKey key) throws IOException {
      		// Clientsocketchannelhaspendingdata
      		SocketChannel clntChan = (SocketChannel) key.channel();
      		ByteBuffer buf = (ByteBuffer) key.attachment();
      		long bytesRead = clntChan.read(buf);
      		if (bytesRead == -1) {// Didtheotherendclose?
      			clntChan.close();
      		} else if (bytesRead > 0) {
      			// Indicateviakeythatreading/writingarebothofinterestnow.
      			key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
      		}
      	}
      
      	public void handleWrite(SelectionKey key) throws IOException {
      		/*
      		 * Channelisavailableforwriting,andkeyisvalid(i.e.,clientchannel
      		 * notclosed).
      		 */
      		// Retrievedatareadearlier
      		ByteBuffer buf = (ByteBuffer) key.attachment();
      		buf.flip();// Preparebufferforwriting
      		SocketChannel clntChan = (SocketChannel) key.channel();
      		clntChan.write(buf);
      		if (!buf.hasRemaining()) {// Buffercompletelywritten?
      		// Nothingleft,sonolongerinterestedinwrites
      			key.interestOps(SelectionKey.OP_READ);
      		}
      		buf.compact();// Makeroomformoredatatobereadin
      	}
      }

      在这里,我们又进一步对Selector注册了相关的事件:key.interestOps(SelectionKey.OP_READ);

      这样,我们就实现了基于NIO的Echo 系统。

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值