NIO入门实例-写一个客户端/服务器例程

注:该文章实际为《java网络编程》例11-1和例11-2的源码勘误!原例程经过实际测试并不能实现期望的功能,在分析代码逻辑后勘误如下!

在勘误之前贴出书中原始例程(仅服务器有误):

package org.nioTest;

import sun.rmi.runtime.Log;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Logger;

public class Servser {

  private final static Logger log = Logger.getLogger("Server.class");
  private static int client_num = 0;

  public static void main(String[] args) {
    byte[] rotation = new byte[95 * 2];
    for (byte i = ' '; i <= '~'; i++) {
      rotation[i - ' '] = i;
      rotation[i + 95 - ' '] = i;
    }

    ServerSocketChannel serverSocketChannel;
    Selector selector;
    try {
      serverSocketChannel = ServerSocketChannel.open();
      ServerSocket serverSocket = serverSocketChannel.socket();
      InetSocketAddress address = new InetSocketAddress("localhost", 5000);
      serverSocket.bind(address);
      serverSocketChannel.configureBlocking(false);

      selector = Selector.open();
      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
      log.info("服务器通道" + client_num++ + "注册完成->" + serverSocketChannel.toString());
    } catch (IOException e) {
      e.printStackTrace();
      return;
    }

    /**
    错误一、当遍历选择器后,起初result = 1,而后result将为0,
    然后无限循环;并不能跳出循环执行接下来的代码!
    **/

    while (true) {
      try {
        int result = selector.select();
        log.info("开始遍历当前注册为选择器的通道" + result);
      } catch (IOException e) {
        e.printStackTrace();
        break;
      }
    }

    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    log.info("获取所有现在注册的通道:");
    Iterator<SelectionKey> iterator = selectionKeys.iterator();
    while (iterator.hasNext()) {
      SelectionKey selectionKey = iterator.next();
      log.info("当前通道:" + selectionKey.toString());
      iterator.remove();
      try {
        if (selectionKey.isAcceptable()) {
          log.info("当前通道是处理连接的通道,准备取出该通道创建对等端通道(该对等通道是写通道)");
          ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
          try {
            SocketChannel client = server.accept();
            log.info("接收客户端的连接:" + client);
            client.configureBlocking(false);
            /**
            错误二:即使错误一解决掉,也就是说跳出循环(解决方式实例:
            if (result != 0) {
                  log.info("已经有选择器就绪");
                  break;
            }
            )
            代码原意是当建立一个连接后就继续注册一个“写通道”,
            这样当循环后就注册器列表就会多出一个通道(即不为0),
            就可以继续判断当前通道属性,然后读数据!但是回过头
            看该代码所在的循环就会发现,注册器列表Set只生成一次:
            Set<SelectionKey> selectionKeys =   selector.selectedKeys();
            log.info("获取所有现在注册的通道:");
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            即使此次又注册了一个注册器(通道),并不会更新此时
            注册器列表的迭代器,这个时候执行完此次循环后迭代器
            为0,直接跳出循环了!所以正确的做法是:每次重新注
            册一个选择器后就要重新遍历所有的选择器,然后形成新
            的注册器列表Set!所以,这个while循环其实应该放在
            上一个while循环里!
            **/
            SelectionKey selectionKey_write = client.register(selector, SelectionKey.OP_WRITE);
            log.info("把该通道继续注册到选择器中");
            ByteBuffer byteBuffer = ByteBuffer.allocate(74);
            byteBuffer.put(rotation, 0, 72);
            byteBuffer.put((byte) '\r');
            byteBuffer.put((byte) '\n');
            byteBuffer.flip();
            log.info("把待发出的数据绑定到写选择器(写通道)上");
            selectionKey_write.attach(byteBuffer);
          } catch (IOException e) {
            e.printStackTrace();
          }
        } else if (selectionKey.isWritable()) {
          log.info("当前通道是处理写数据的通道,准备取出该通道和该通道绑定的数据(用于输出数据)");
          SocketChannel client = (SocketChannel) selectionKey.channel();
          ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
          if (!byteBuffer.hasRemaining()) {
            byteBuffer.rewind();
            int first = byteBuffer.get();
            byteBuffer.rewind();
            int postion = first - ' ' + 1;
            byteBuffer.put(rotation, postion, 72);
            byteBuffer.put((byte) '\r');
            byteBuffer.put((byte) '\n');
            byteBuffer.flip();
          }
          client.write(byteBuffer);
        }
      } catch (IOException e) {
        selectionKey.cancel();
        try {
          selectionKey.channel().close();
        } catch (IOException e1) {
          e1.printStackTrace();
        }
        e.printStackTrace();
      }
    }
  }
}

代码错误原因已经在源码中说明!这是修改过的例程:
客户端

package org.nioTest;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.logging.Logger;

public class Client {
  private final static Logger log = Logger.getLogger("Client.class");
  public static void main(String[] args) {
    try{
      SocketAddress address = new InetSocketAddress("localhost",5000);
      SocketChannel socketChannel_client = SocketChannel.open(address);
      log.info("绑定服务器完成"+socketChannel_client.toString());

      ByteBuffer byteBuffer = ByteBuffer.allocate(74);
      WritableByteChannel out = Channels.newChannel(System.out);
      while(socketChannel_client.read(byteBuffer) != -1){
        byteBuffer.flip();
        out.write(byteBuffer);
        byteBuffer.clear();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

服务器:

package org.nioTest;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Logger;

public class Servser {

  private final static Logger log = Logger.getLogger("Server.class");
  private static int client_num = 0;

  public static void main(String[] args) {
    byte[] rotation = new byte[95 * 2];
    for (byte i = ' '; i <= '~'; i++) {
      rotation[i - ' '] = i;
      rotation[i + 95 - ' '] = i;
    }

    ServerSocketChannel serverSocketChannel;
    Selector selector;
    try {
      serverSocketChannel = ServerSocketChannel.open();
      ServerSocket serverSocket = serverSocketChannel.socket();
      InetSocketAddress address = new InetSocketAddress("localhost", 5000);
      serverSocket.bind(address);
      serverSocketChannel.configureBlocking(false);

      selector = Selector.open();
      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
      log.info("服务器通道" + client_num++ + "注册完成->" + serverSocketChannel.toString());
    } catch (IOException e) {
      e.printStackTrace();
      return;
    }

    while (true) {
      try {
        int result = selector.select();
        log.info("开始遍历当前注册为选择器的通道" + result);
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        log.info("获取所有现在注册的通道:");
        Iterator<SelectionKey> iterator = selectionKeys.iterator();
        /*注意此时的while循环放在这里*/
        while (iterator.hasNext()) {
          SelectionKey selectionKey = iterator.next();
          log.info("当前通道:" + selectionKey.toString());
          iterator.remove();
          try {
            if (selectionKey.isAcceptable()) {
              log.info("当前通道是处理连接的通道,准备取出该通道创建对等端通道(该对等通道是写通道)");
              ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
              try {
                SocketChannel client = server.accept();
                log.info("接收客户端的连接:" + client);
                client.configureBlocking(false);
                SelectionKey selectionKey_write = client.register(selector, SelectionKey.OP_WRITE);
                iterator = selector.selectedKeys().iterator();
                log.info("把该通道继续注册到选择器中");
                ByteBuffer byteBuffer = ByteBuffer.allocate(74);
                byteBuffer.put(rotation, 0, 72);
                byteBuffer.put((byte) '\r');
                byteBuffer.put((byte) '\n');
                byteBuffer.flip();
                log.info("把待发出的数据绑定到写选择器(写通道)上");
                selectionKey_write.attach(byteBuffer);
              } catch (IOException e) {
                e.printStackTrace();
              }
            } else if (selectionKey.isWritable()) {
              log.info("当前通道是处理写数据的通道,准备取出该通道和该通道绑定的数据(用于输出数据)");
              SocketChannel client = (SocketChannel) selectionKey.channel();
              ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
              if (!byteBuffer.hasRemaining()) {
                byteBuffer.rewind();
                int first = byteBuffer.get();
                byteBuffer.rewind();
                int postion = first - ' ' + 1;
                byteBuffer.put(rotation, postion, 72);
                byteBuffer.put((byte) '\r');
                byteBuffer.put((byte) '\n');
                byteBuffer.flip();
              }
              client.write(byteBuffer);
            }
          } catch (IOException e) {
            selectionKey.cancel();
            try {
              selectionKey.channel().close();
            } catch (IOException e1) {
              e1.printStackTrace();
            }
            e.printStackTrace();
          }
        }
      } catch (IOException e) {
        e.printStackTrace();
        break;
      }
    }
  }
}

修改过后就可正常运行了!可以试着开一个服务器,同时开n个客户端来验证非阻塞模式下单线程也能同时处理多个请求的效果!
这里写图片描述

这里并没有详细叙述NIO的相关知识,所以关于NIO的基本概念、入门教程请参考:
(1)Java NIO系列教程
(2)JAVA网络编程第四版中文

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值