`
jarbee
  • 浏览: 28315 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

[转]NIO Client程序片断

    博客分类:
  • java
阅读更多
The client

Before getting started it's worth mentioning that the client implementation is going to end up looking a lot like the server implementation we just finished writing. There's going to be a lot of common functionality that can be factored out and shared between the client and server implementation. For the most part I'm going to totally ignore that and write the client as a standalone piece of code to avoid clouding the picture with layer upon layer of abstraction. I'll leave that as an exercise to the reader. However, there are one or two little bits of code we've already written that will be shared between the implementations, because not doing so may reduce clarity. It's a judgement call on my part so if you disagree, forgive me and try not to let it bother you too much.

That said, let's get on with the client code. Once again, we're going to need some infrastructure. We can actually borrow a lot from the server code, since once a connection is accepted/established there's not a lot of difference between a client and server. A client still needs to read and write data, so at the very least we're going to need that logic. Given the similarities I'm going to build the client implementation by starting with the server implementation and tweaking it as needed.

First, let's remove some bits we don't need. The server socket channel can go, we won't be needing that. We can also remove the accept() method and the logic in the run() method that invokes it.

Next we can throw out most of the logic in the initSelector() method. In fact, we only need the first line, leaving us with a method that looks like this.
 private Selector initSelector() throws IOException {
    // Create a new selector
    return SelectorProvider.provider().openSelector();
  }



We're still missing the most important piece of client-side logic: establishing a connection. This usually goes hand in hand with wanting to send some data. There are scenarios where that's not true: you might, for example, want to establish a bunch of connections to a remote server up front and then send all traffic over those connections. I'm not going to take that approach but it's not hard to get there from here. While we're on the topic of what I'm not going to do, I'm also not going to implement connection pooling. I'm not going to implement a client that talks to a bunch of remote servers on different addresses; the client here will talk to one remote server (albeit using multiple simultaneous connections). What I am going to implement is a client that establishes a new connection, sends a message, waits for a response and then disconnects (oh, and I'm not going to handle the case where a response isn't received). Anything beyond that adds very little beyond more code to confuse the issue.

Phew, that paragraph really got away from me. Back to the code! What we'll need is a send() method that requests a new connection and queues data to be sent on that connection when it comes up. We'll also need some way of notifying the caller when the response is received, but one step at a time.

But before we can put together the send() method we really need a method for initiating a new connection. The send() method will make more sense if we introduce initiateConnection() first.
private SocketChannel initiateConnection() throws IOException {
    // Create a non-blocking socket channel
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false);
  
    // Kick off connection establishment
    socketChannel.connect(new InetSocketAddress(this.hostAddress, this.port));
  
    // Queue a channel registration since the caller is not the 
    // selecting thread. As part of the registration we'll register
    // an interest in connection events. These are raised when a channel
    // is ready to complete connection establishment.
    synchronized(this.pendingChanges) {
      this.pendingChanges.add(new ChangeRequest(socketChannel, ChangeRequest.REGISTER, SelectionKey.OP_CONNECT));
    }
    
    return socketChannel;
  }

 

In a nutshell, initiating a connection means creating a new non-blocking socket channel, initiating a (non-blocking) connection and registering an interest in finishing the connection. The only wrinkle is that, since the calling thread is not the selecting thread, the last step must be deferred. We don't wake the selecting thread up because the method that calls this will want to queue some data to be written when the connection is completed. Waking the selecting thread up here opens us up to a race condition where the connection is completed by the selecting thread before the calling thread queues the data and OP_WRITE is never set on the channel's selection key.

Given the requirements we discussed earlier and the initiateConnection() above, our send() method needs to look something like this (with an additional instance member which will be clarified later).

// Maps a SocketChannel to a RspHandler
  private Map rspHandlers = Collections.synchronizedMap(new HashMap());
  

  public void send(byte[] data, RspHandler handler) throws IOException {
    // Start a new connection
    SocketChannel socket = this.initiateConnection();
    
    // Register the response handler
    this.rspHandlers.put(socket, handler);
    
    // And queue the data we want written
    synchronized (this.pendingData) {
      List queue = (List) this.pendingData.get(socket);
      if (queue == null) {
        queue = new ArrayList();
        this.pendingData.put(socket, queue);
      }
      queue.add(ByteBuffer.wrap(data));
    }

    // Finally, wake up our selecting thread so it can make the required changes
    this.selector.wakeup();
  }


It's important to note that nowhere in the two methods just introduced do we request that the OP_CONNECT flag be set on the socket channel's selection key. If we did that we'd overwrite the OP_CONNECT flag and never complete the connection. And if we combined them then we'd run the risk of trying to write on an unconnected channel (or at least having to deal with that case). Instead what we'll do is set the OP_WRITE flag when the connection is established (we could do this based on whether or not data was queued but for our scenario it's acceptable to do it this way).

Which brings us to the second change to our run() method. Remember that we started out with the server implementation, so we still have the logic for handling a pending change to the interest operation set for a selection key. To that we need to handle a pending channel registration. Voila...

 switch (change.type) {
            case ChangeRequest.CHANGEOPS:
              SelectionKey key = change.socket.keyFor(this.selector);
              key.interestOps(change.ops);
              break;
            case ChangeRequest.REGISTER:
              change.socket.register(this.selector, change.ops);
              break;
            }

          

And, of course, we'll need to handle the connectable event...

          // Check what event is available and deal with it
          if (key.isConnectable()) {
            this.finishConnection(key);
          } else if (key.isReadable()) {
            this.read(key);
          } else if (key.isWritable()) {
            this.write(key);
          }


And of course we need an implementation for finishConnection().
 private void finishConnection(SelectionKey key) throws IOException {
    SocketChannel socketChannel = (SocketChannel) key.channel();
  
    // Finish the connection. If the connection operation failed
    // this will raise an IOException.
    try {
      socketChannel.finishConnection();
    } catch (IOException e) {
      // Cancel the channel's registration with our selector
      key.cancel();
      return;
    }
  
    // Register an interest in writing on this channel
    key.interestOps(SelectionKey.OP_WRITE);
  }



As I mentioned before, once the connection is complete we immediately register an interest in writing on the channel. Data has already been queued (or we wouldn't be establishing a connection in the first place). Since the current thread in this case is the selecting thread we're free to modify the selection key directly.

One last change before we're ready to put it all together. Our server implementation's read() handed the data off to a worker thread (that just echoed it back). For the client side we really want to hand the data (the response) back to whoever initiated the original send (the request). Scroll back up the page until you find mention of a RspHandler (it was passed as a parameter to send()). That's our conduit back to the original caller and it replaces our call to the EchoServer in the read() method. So the last line looks like this:
// Handle the response
    this.handleResponse(socketChannel, this.readBuffer.array(), numRead);

   

Which begs an implementation of handleResponse. All this method has to do is look up the handler we stashed for this channel and pass it the data we've read. We let the handler indicate whether or not it's seen enough. If it has we'll close the connection.
private void handleResponse(SocketChannel socketChannel, byte[] data, int numRead) throws IOException {
    // Make a correctly sized copy of the data before handing it
    // to the client
    byte[] rspData = new byte[numRead];
    System.arraycopy(data, 0, rspData, 0, numRead);
    
    // Look up the handler for this channel
    RspHandler handler = (RspHandler) this.rspHandlers.get(socketChannel);
    
    // And pass the response to it
    if (handler.handleResponse(rspData)) {
      // The handler has seen enough, close the connection
      socketChannel.close();
      socketChannel.keyFor(this.selector).cancel();
    }
  }


 
The response handler itself is pretty simple but for it to make complete sense we need to see how all of this hangs together in the form of our client's main() method.
 public static void main(String[] args) {
    try {
      NioClient client = new NioClient(InetAddress.getByName("localhost"), 9090);
      Thread t = new Thread(client);
      t.setDaemon(true);
      t.start();
      RspHandler handler = new RspHandler();
      client.send("Hello World".getBytes(), handler);
      handler.waitForResponse();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }



The response handler implementation is shown below. I waited until last so that the waitForResponse() would make sense, along with some of the synchronization logic. It's entirely possible to pull this logic into the send() method, effectively making it a synchronous call. But this tutorial is illustrating asynchronous I/O so that seems kind of silly. It's not as silly as it sounds, mind you, since you might want to handle a large number of active connections even if the client is forced to use them one at a time. But this illustrates how to perform the send asynchronously. Everything else is merely details.

Quoted from: http://www.javafaq.nu/java-article1103.html
分享到:
评论

相关推荐

    java nio im(server+client)

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java从1.4版本开始引入的一种新的I/O模型,相对于传统的BIO( Blocking I/O)模型,NIO在处理高并发、大数据量的网络应用时表现出更高的效率和...

    android nio程序

    本篇将深入探讨`android nio`程序,主要关注`NioServer`和`NioClient`两个示例程序。 ### 一、`java.nio`概述 `java.nio`是Java平台提供的一种I/O模型,与传统的`java.io`流模型相比,具有以下优势: 1. **缓冲区...

    JAVA-NIO程序设计完整实例

    **JAVA-NIO程序设计完整实例** Java NIO(New IO)是Java 1.4引入的一个新特性,它为Java提供了非阻塞I/O操作的能力,使得Java在处理I/O时更加高效。NIO与传统的BIO(Blocking I/O)模型相比,其核心在于它允许程序...

    java NIO实例

    `NIOServer.java`和`NIOClient.java`这两个文件很可能是用于演示Java NIO服务器端和客户端的基本操作。下面将详细介绍Java NIO的主要组件和工作原理,并结合这两个文件名推测它们可能包含的内容。 1. **Selector...

    NioSocket,包括server端和client端

    NioSocket是一个基于Java NIO(非阻塞I/O)技术实现的网络通信框架,它包含服务器端(Server)和客户端(Client)两部分。在Java编程中,NIO(New Input/Output)提供了一种不同于传统IO模型的I/O操作方式,其核心...

    java NIO 学习 聊天室程序 (3)

    在这个“Java NIO 学习 聊天室程序”项目中,我们将深入探讨NIO如何用于创建一个聊天室程序。这个程序可能包含以下几个关键部分: 1. **服务器端**:服务器端使用NIO的ServerSocketChannel监听客户端连接。当新的...

    java nio socket 例子

    本例包含服务器端和客户端,多线程,每线程多次发送,Eclipse工程,启动服务器使用 nu.javafaq.server.NioServer,启动客户端使用 nu.javafaq.client.NioClient。另本例取自javafaq.nv上的程序修改而成

    mina框架--MINA框架是对java的NIO包的一个封装

    MINA(Multipurpose Infrastructure for Network Applications)框架是Apache软件基金会的一个开源项目,它提供了一种高级的、事件驱动的网络应用程序框架...

    JAVA NIO 聊天室程序

    **JAVA NIO 聊天室程序** 在Java编程领域,网络编程是一个重要的部分,而NIO(Non-blocking Input/Output)是Java提供的一种高效、非阻塞的I/O模型,它极大地提升了处理大量并发连接的能力。这个"JAVA NIO 聊天室...

    java NIO.zip

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统的I/O模型的新技术。自Java 1.4版本引入NIO后,它为Java开发者提供了更高效的数据传输方式,尤其是在处理大量并发...

    java_Nio_server_and_j2me_client.rar_J2ME SERVER_NIO_j2me_j2me ni

    而"TestNIO_client"和"TestNIO_server"这两个文件名很可能分别对应的是NIO客户端和服务器的源代码文件,用户可以通过这些源代码了解如何在J2ME和Java NIO之间建立有效的通信。 在实际应用中,J2ME客户端可能使用...

    nio demo for nio学习笔记(体系结构以及模块介绍)

    而NIO则采用了非阻塞模式,允许程序在等待数据准备就绪的过程中去做其他事情,提高了程序的效率和并发性。 **NIO的核心组件:** 1. **Channel(通道)**:Channel是数据传输的双向管道,可以同时进行读写操作。例如...

    nio.rar_NIO_NIO-socket_java nio_java 实例_java.nio

    Java NIO(New IO)是Java 1.4版本引入的一个新特性,它为Java应用程序提供了非阻塞I/O操作的能力,与传统的IO模型(基于流的I/O和基于缓冲区的I/O)相比,NIO具有更高的效率和灵活性。在Java NIO中,数据是以通道...

    JAVA-NIO-DEMO

    本示例"JAVA-NIO-DEMO"提供了关于Java NIO的实际应用,通过Anontion(注解)、Applet(小程序)和NIO的Demo,帮助开发者更深入地理解和掌握这些概念。 首先,让我们深入了解Java NIO。NIO的核心组件包括: 1. **...

    基于java NIO的socket通信demo

    在`NioClient.java`中,客户端同样使用NIO进行通信。客户端首先创建一个`SocketChannel`,然后向服务器的`ServerSocketChannel`发起连接请求。接着,客户端可以设置自己的`Charset`来确保数据在传输过程中不出现乱码...

    NIO 入门.chm,NIO 入门.chm

    2. **缓冲区(Buffer)**:缓冲区是NIO中数据存储的主要对象,它是内存块的抽象,用于在通道和应用程序之间传输数据。Java NIO提供了多种类型的缓冲区,如ByteBuffer、CharBuffer、IntBuffer、FloatBuffer等,每种...

    JavaNIO chm帮助文档

    Java NIO系列教程(一) Java NIO 概述 Java NIO系列教程(二) Channel Java NIO系列教程(三) Buffer Java NIO系列教程(四) Scatter/Gather Java NIO系列教程(五) 通道之间的数据传输 Java NIO系列教程(六)...

    javaNiO.doc

    ### Java NIO (New IO) 详解 #### 1. 引言 在Java的世界里,I/O(Input/Output)操作是程序与外部环境进行交互的重要方式之一。随着技术的发展,传统I/O模型逐渐显露出一些局限性,特别是在处理高并发场景下,其...

    Java NIO英文高清原版

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java平台中用于替代标准I/O(BIO)模型的一种新机制。NIO在Java 1.4版本引入,提供了更高效的数据处理和通道通信方式,特别适用于高并发、大数据...

Global site tag (gtag.js) - Google Analytics