多个SocketChannel注册Selector统一管理

虽然我们可以自己处理每一个Socket事件,比如读写数据,不过更常规的方式是注册一个选择器。这个选择器侦听着数据的变化事件。 每个注册的通道都有自己的SelectionKey,用这个可以区分到底是哪个通道产生了事件。

看代码
  1. package net.java2000.nio;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  4. import java.nio.ByteBuffer;
  5. import java.nio.channels.SelectionKey;
  6. import java.nio.channels.Selector;
  7. import java.nio.channels.SocketChannel;
  8. import java.util.Iterator;
  9. /**
  10.  * 多个SocketChannel注册Selector。
  11.  * 
  12.  * @author 赵学庆,Java世纪网(java2000.net)
  13.  * 
  14.  */
  15. public class SocketChannelSelector {
  16.   public static SocketChannel createSocketChannel(String hostName, int port)
  17.       throws IOException {
  18.     SocketChannel sChannel = SocketChannel.open();
  19.     sChannel.configureBlocking(false);
  20.     sChannel.connect(new InetSocketAddress(hostName, port));
  21.     return sChannel;
  22.   }
  23.   // 2个连接注册的选择器关键字
  24.   static SelectionKey key1;
  25.   static SelectionKey key2;
  26.   public static void main(String[] args) {
  27.     // 1个选择器,注册2个Socket 通道
  28.     Selector selector = null;
  29.     try {
  30.       // 创建选择器
  31.       selector = Selector.open();
  32.       // 创建2个通道
  33.       SocketChannel sChannel1 = createSocketChannel("163.net"25);
  34.       SocketChannel sChannel2 = createSocketChannel("mail.csdn.net"25);
  35.       // 注册选择器,侦听所有的事件
  36.       key1 = sChannel1.register(selector, sChannel1.validOps());
  37.       key2 = sChannel2.register(selector, sChannel1.validOps());
  38.     } catch (IOException e) {
  39.     }
  40.     // 等待事件的循环
  41.     while (true) {
  42.       try {
  43.         // 等待
  44.         selector.select();
  45.       } catch (IOException e) {
  46.         break;
  47.       }
  48.       // 所有事件列表
  49.       Iterator<SelectionKey> it = selector.selectedKeys().iterator();
  50.       // 处理每一个事件
  51.       while (it.hasNext()) {
  52.         // 得到关键字
  53.         SelectionKey selKey = it.next();
  54.         // 删除已经处理的关键字
  55.         it.remove();
  56.         try {
  57.           // 处理事件
  58.           processSelectionKey(selKey);
  59.         } catch (IOException e) {
  60.           // 处理异常
  61.           selKey.cancel();
  62.         }
  63.       }
  64.     }
  65.   }
  66.   public static void processSelectionKey(SelectionKey selKey) throws IOException {
  67.     ByteBuffer buf = ByteBuffer.allocateDirect(1024);
  68.     // 确认连接正常
  69.     if (selKey.isValid() && selKey.isConnectable()) {
  70.       // 得到通道
  71.       SocketChannel sChannel = (SocketChannel) selKey.channel();
  72.       // 是否连接完毕?
  73.       boolean success = sChannel.finishConnect();
  74.       if (!success) {
  75.         // 异常
  76.         selKey.cancel();
  77.       }
  78.     }
  79.     // 如果可以读取数据
  80.     if (selKey.isValid() && selKey.isReadable()) {
  81.       // 得到通道
  82.       SocketChannel sChannel = (SocketChannel) selKey.channel();
  83.       if (sChannel.read(buf) > 0) {
  84.         // 转到最开始
  85.         buf.flip();
  86.         while (buf.remaining() > 0) {
  87.           System.out.print((char) buf.get());
  88.         }
  89.         // 也可以转化为字符串,不过需要借助第三个变量了。
  90.         // buf.get(buff, 0, numBytesRead);
  91.         // System.out.println(new String(buff, 0, numBytesRead, "UTF-8"));
  92.         // 复位,清空
  93.         buf.clear();
  94.       }
  95.     }
  96.     // 如果可以写入数据
  97.     if (selKey.isValid() && selKey.isWritable()) {
  98.       // 得到通道
  99.       SocketChannel sChannel = (SocketChannel) selKey.channel();
  100.       // 区分2个侦听器的关键字
  101.       // 我这里只写一次数据。
  102.       if (!s1 && key1.equals(selKey)) {
  103.         System.out.println("channel1 write data..");
  104.         buf.clear();
  105.         buf.put("HELO localhost/n".getBytes());
  106.         buf.flip();
  107.         sChannel.write(buf);
  108.         s1 = true;
  109.       } else if (!s2 && key2.equals(selKey)) {
  110.         System.out.println("channel2 write data..");
  111.         buf.clear();
  112.         buf.put("HELO localhost/n".getBytes());
  113.         buf.flip();
  114.         sChannel.write(buf);
  115.         s2 = true;
  116.       }
  117.     }
  118.   }
  119.   // 判断已经写过数据的标志
  120.   static boolean s1 = false;
  121.   static boolean s2 = false;
  122. }

一个运行的效果
channel1 write data..
220 Coremail SMTP(Anti Spam) System (163net[040302])
250 bjfee_app2
channel2 write data..
220 csdn.net ESMTP "can't tell you"
250 csdn.net


结论
  这样做,我们的多个数据通道就可以统一管理,分别处理器读写操作。
要使用`Selector`来监听多个`SocketChannel`,可以按照以下步骤进行: 1. 创建一个`Selector`对象:使用`Selector.open()`方法创建一个`Selector`对象,用于管理多个`SocketChannel`的事件。 2. 将`SocketChannel`注册到`Selector`上:对于每个需要监听的`SocketChannel`,调用其`register()`方法将其注册到`Selector上,并指定监听的事件型,如`SelectionKey.OP_READ`表示可读事件。 3. 调用`Selector.select()`方法进行事件监听:调用`Selector`的`select()`方法进行事件监听。该方法会阻塞,直到有一个或多个注册的事件发生。 4. 处理发生的事件:调用`Selector`的`selectedKeys()`方法获取发生事件的`SelectionKey`集合,遍历这些`SelectionKey`,根据事件型进行相应处理(如读取数据、写入数据等)。 5. 取消已处理的事件:处理完一个事件后,可以通过调用`SelectionKey.cancel()`方法取消该事件的监听,以便下次再次监听。 6. 重复执行步骤3-5:循环执行步骤3-5,实现持续的事件监听和处理。 原理:`Selector`利用操作系统提供的多路复用机制来实现对多个`SocketChannel`的监听。当调用`select()`方法时,会阻塞等待直到至少有一个注册的事件发生。当有事件发生时,可以通过`selectedKeys()`方法获取发生事件的`SelectionKey`集合,并根据事件型进行相应的处理。 通过使用`Selector`,可以将多个`SocketChannel`的IO操作集中到一个线程中进行处理,提高了系统的性能和资源利用率。同时,非阻塞IO的特性使得在没有可读写数据时,线程可以立即返回,不需要阻塞等待,提高了系统的响应速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值