[转帖]在Java中使用NIO进行网络编程_Tomcat, WebLogic及J2EE讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Tomcat, WebLogic及J2EE讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3923 | 回复: 0   主题: [转帖]在Java中使用NIO进行网络编程        下一篇 
masy
注册用户
等级:少校
经验:1234
发帖:182
精华:0
注册:2011-11-4
状态:离线
发送短消息息给masy 加好友    发送短消息息给masy 发消息
发表于: IP:您无权察看 2011-11-18 16:20:32 | [全部帖] [楼主帖] 楼主

在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 系统。




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论