在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  3))//  Testforcorrect#ofargs    throw new IllegalArgumentException(      "Parameter(s):   []");   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):...");   }   // 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 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 系统。

有人要进来,有一些人不得不离开。

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

相关文章:

你感兴趣的文章:

标签云: