简介

NIO有三宝:Buffer,Channel,Selector少不了。本文将会介绍NIO三件套中的最后一套Selector,并在理解Selector的基础上,协助小师妹发一张好人卡。我们开始吧。

Selector介绍

小师妹:F师兄,最近我的桃花有点旺,好几个师兄莫名其妙的跟我打招呼,可是我一心向着工作,不想谈论这些事情。毕竟先有事业才有家嘛。我又不好直接拒绝,有没有什么比较隐晦的方法来让他们放弃这个想法?

更多内容请访问www.flydean.com

这个问题,我沉思了大约0.001秒,于是给出了答案:给他们发张好人卡吧,应该就不会再来纠缠你了。

小师妹:F师兄,如果给他们发完好人卡还没有用呢?

那就只能切断跟他们的联系了,来个一刀两断。哈哈。

这样吧,小师妹你最近不是在学NIO吗?刚好我们可以用Selector来模拟一下发好人卡的过程。

假如你的志伟师兄和子丹师兄想跟你建立联系,每个人都想跟你建立一个沟通通道,那么你就需要创建两个channel。

两个channel其实还好,如果有多个人都想同时跟你建立联系通道,那么要维持这些通道就需要保持连接,从而浪费了资源。

但是建立的这些连接并不是时时刻刻都有消息在传输,所以其实大多数时间这些建立联系的通道其实是浪费的。

如果使用Selector就可以只启用一个线程来监听通道的消息变动,这就是Selector。

从上面的图可以看出,Selector监听三个不同的channel,然后交给一个processor来处理,从而节约了资源。

创建Selector

先看下selector的定义:

public abstract class Selector implements Closeable

Selector是一个abstract类,并且实现了Closeable,表示Selector是可以被关闭的。

虽然Selector是一个abstract类,但是可以通过open来简单的创建:

Selector selector = Selector.open();

如果细看open的实现可以发现一个很有趣的现象:

public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}

open方法调用的是SelectorProvider中的openSelector方法。

再看下provider的实现:

 public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});

有三种情况可以加载一个SelectorProvider,如果系统属性指定了java.nio.channels.spi.SelectorProvider,那么从指定的属性加载。

如果没有直接指定属性,则从ServiceLoader来加载。

最后如果都找不到的情况下,使用默认的DefaultSelectorProvider。

关于ServiceLoader的用法,我们后面会有专门的文章来讲述。这里先不做多的解释。

注册Selector到Channel中

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("localhost", 9527));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

如果是在服务器端,我们需要先创建一个ServerSocketChannel,绑定Server的地址和端口,然后将Blocking设置为false。因为我们使用了Selector,它实际上是一个非阻塞的IO。

注意FileChannels是不能使用Selector的,因为它是一个阻塞型IO。

小师妹:F师兄,为啥FileChannel是阻塞型的呀?做成非阻塞型的不是更快?

小师妹,我们使用FileChannel的目的是什么?就是为了读文件呀,读取文件肯定是一直读一直读,没有可能读一会这个channel再读另外一个channel吧,因为对于每个channel自己来讲,在文件没读取完之前,都是繁忙状态,没有必要在channel中切换。

最后我们将创建好的Selector注册到channel中去。

SelectionKey

SelectionKey表示的是我们希望监听到的事件。

总的来说,有4种Event:

  • SelectionKey.OP_READ 表示服务器准备好,可以从channel中读取数据。
  • SelectionKey.OP_WRITE 表示服务器准备好,可以向channel中写入数据。
  • SelectionKey.OP_CONNECT 表示客户端尝试去连接服务端
  • SelectionKey.OP_ACCEPT 表示服务器accept一个客户端的请求
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;

我们可以看到上面的4个Event是用位运算来定义的,如果将这个四个event使用或运算合并起来,就得到了SelectionKey中的interestOps。

和interestOps类似,SelectionKey还有一个readyOps。

一个表示感兴趣的操作,一个表示ready的操作。

最后,SelectionKey在注册的时候,还可以attach一个Object,比如我们可以在这个对象中保存这个channel的id:

SelectionKey key = channel.register(
selector, SelectionKey.OP_ACCEPT, object);
key.attach(Object);
Object object = key.attachment();

object可以在register的时候传入,也可以调用attach方法。

最后,我们可以通过key的attachment方法,获得该对象。

selector 和 SelectionKey

我们通过selector.select()这个一个blocking操作,来获取一个ready的channel。

然后我们通过调用selector.selectedKeys()来获取到SelectionKey对象。

在SelectionKey对象中,我们通过判断ready的event来处理相应的消息。

总的例子

接下来,我们把之前将的串联起来,先建立一个小师妹的ChatServer:

public class ChatServer {

    private static String BYE_BYE="再见";

    public static void main(String[] args) throws IOException, InterruptedException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("localhost", 9527));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer byteBuffer = ByteBuffer.allocate(512); while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey selectionKey = iter.next();
if (selectionKey.isAcceptable()) {
register(selector, serverSocketChannel);
}
if (selectionKey.isReadable()) {
serverResonse(byteBuffer, selectionKey);
}
iter.remove();
}
Thread.sleep(1000);
}
} private static void serverResonse(ByteBuffer byteBuffer, SelectionKey selectionKey)
throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.read(byteBuffer);
byteBuffer.flip();
byte[] bytes= new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
log.info(new String(bytes).trim());
if(new String(bytes).trim().equals(BYE_BYE)){
log.info("说再见不如不见!");
socketChannel.write(ByteBuffer.wrap("再见".getBytes()));
socketChannel.close();
}else {
socketChannel.write(ByteBuffer.wrap("你是个好人".getBytes()));
}
byteBuffer.clear();
} private static void register(Selector selector, ServerSocketChannel serverSocketChannel)
throws IOException {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}

上面例子有两点需要注意,我们在循环遍历中,当selectionKey.isAcceptable时,表示服务器收到了一个新的客户端连接,这个时候我们需要调用register方法,再注册一个OP_READ事件到这个新的SocketChannel中,然后继续遍历。

第二,我们定义了一个stop word,当收到这个stop word的时候,会直接关闭这个client channel。

再看看客户端的代码:

public class ChatClient {

    private static SocketChannel socketChannel;
private static ByteBuffer byteBuffer; public static void main(String[] args) throws IOException { ChatClient chatClient = new ChatClient();
String response = chatClient.sendMessage("hello 小师妹!");
log.info("response is {}", response);
response = chatClient.sendMessage("能不能?");
log.info("response is {}", response);
chatClient.stop(); } public void stop() throws IOException {
socketChannel.close();
byteBuffer = null;
} public ChatClient() throws IOException {
socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9527));
byteBuffer = ByteBuffer.allocate(512);
} public String sendMessage(String msg) throws IOException {
byteBuffer = ByteBuffer.wrap(msg.getBytes());
String response = null;
socketChannel.write(byteBuffer);
byteBuffer.clear();
socketChannel.read(byteBuffer);
byteBuffer.flip();
byte[] bytes= new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
response =new String(bytes).trim();
byteBuffer.clear();
return response; }
}

客户端代码没什么特别的,需要注意的是Buffer的读取。

最后输出结果:

server收到: INFO com.flydean.ChatServer - hello 小师妹!
client收到: INFO com.flydean.ChatClient - response is 你是个好人
server收到: INFO com.flydean.ChatServer - 能不能?
client收到: INFO com.flydean.ChatClient - response is 再见

解释一下整个流程:志伟跟小师妹建立了一个连接,志伟向小师妹打了一个招呼,小师妹给志伟发了一张好人卡。志伟不死心,想继续纠缠,小师妹回复再见,然后自己关闭了通道。

总结

本文介绍了Selector和channel在发好人卡的过程中的作用。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/java-io-nio-selector/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

小师妹学JavaIO之:用Selector来发好人卡的更多相关文章

  1. 小师妹学JavaIO之:NIO中Channel的妙用

    目录 简介 Channel的分类 FileChannel Selector和Channel DatagramChannel SocketChannel ServerSocketChannel Asyn ...

  2. 小师妹学JavaIO之:目录还是文件

    目录 简介 linux中的文件和目录 目录的基本操作 目录的进阶操作 目录的腰疼操作 总结 简介 目录和文件傻傻分不清楚,目录和文件的本质到底是什么?在java中怎么操纵目录,怎么遍历目录.本文F师兄 ...

  3. 小师妹学JavaIO之:文件系统和WatchService

    目录 简介 监控的痛点 WatchService和文件系统 WatchSerice的使用和实现本质 总结 简介 小师妹这次遇到了监控文件变化的问题,F师兄给小师妹介绍了JDK7 nio中引入的Watc ...

  4. 小师妹学JavaIO之:文件File和路径Path

    简介 文件和路径有什么关系?文件和路径又隐藏了什么秘密?在文件系统的管理下,创建路径的方式又有哪些?今天F师兄带小师妹再给大家来一场精彩的表演. 文件和路径 小师妹:F师兄我有一个问题,java中的文 ...

  5. 小师妹学JavaIO之:Buffer和Buff

    目录 简介 Buffer是什么 Buffer进阶 创建Buffer Direct VS non-Direct Buffer的日常操作 向Buffer写数据 从Buffer读数据 rewind Buff ...

  6. 小师妹学JavaIO之:MappedByteBuffer多大的文件我都装得下

    目录 简介 虚拟地址空间 详解MappedByteBuffer MapMode MappedByteBuffer的最大值 MappedByteBuffer的使用 MappedByteBuffer要注意 ...

  7. 小师妹学JavaIO之:NIO中那些奇怪的Buffer

    目录 简介 Buffer的分类 Big Endian 和 Little Endian aligned内存对齐 总结 简介 妖魔鬼怪快快显形,今天F师兄帮助小师妹来斩妖除魔啦,什么BufferB,Buf ...

  8. 小师妹学IO系列文章集合-附PDF下载

    目录 第一章 IO的本质 IO的本质 DMA和虚拟地址空间 IO的分类 IO和NIO的区别 总结 第二章 try with和它的底层原理 简介 IO关闭的问题 使用try with resource ...

  9. 小师妹学JVM之:JIT中的PrintAssembly

    目录 简介 使用PrintAssembly 输出过滤 总结 简介 想不想了解JVM最最底层的运行机制?想不想从本质上理解java代码的执行过程?想不想对你的代码进行进一步的优化和性能提升? 如果你的回 ...

随机推荐

  1. Centos7访问Win7/Win10系统中的共享文件

    服务器挂在U盘: 1.先将U盘格式化为Fat格式或其他服务器能识别的格式. 2.使用fdisk -l,找到自己的U盘(根据盘大小) 3.新建一个文件夹(mkdir /mnt/usb)用于挂在数据 4. ...

  2. ORM多表增删改查

    一 创建多表 在models.py里创建4张表:Author(作者).AuthorDetail(作者详细信息).Publish(出版社).Book(书) 四张表关系为: (1)首先创建一对一关系.On ...

  3. 解决WordPress网站被利用xmlrpc.php文件攻击问题

    原因在于使用的WORDPRESS程序默认xmlrpc.php开启,而被用来DDOS攻击导致占用资源过高. 如何解决这个问题呢? 是屏蔽 XML-RPC (pingback) 的功能. add_filt ...

  4. 0521Day03命名规范 Data函数 可变长参数 枚举类型

    [重点] 命名规范 枚举类型 Date函数 可变长参数 pirnt,println 命名规范 1. 驼峰命名法:main,username,setUsername 用于变量.方法的命名 2. Pasc ...

  5. 前端开发Chrome调试工具

    Chrome浏览器提供了一个非常好用的调试工具,可以用来调试我们的HTML结构和CSS样式. 1.的打开调试工具 打开Chrome浏览器,按下F12键或点击页面空白,点击检查 2.使用调试工具 (1) ...

  6. 痞子衡嵌入式:MCUBootUtility v2.3发布,这次不再放过任何一款Flash

    -- 痞子衡的 NXP-MCUBootUtility 开源项目自2018年8月27日第一笔提交至今已有21个月,目前累计代码已近50000行.相信这个工具为大家开发 i.MXRT 项目提供了一些便利, ...

  7. [Objective-C] 012_数据持久化_XML属性列表,NSUserDefaults

    在日常开发中经常要对NSString.NSDictionary.NSArray.NSData.NSNumber这些基本类的数据进行持久化,我们可以用XML属性列表持久化到.plist 文件中,也可以用 ...

  8. SpringCloud Netflix (六):Config 配置中心

    ------------恢复内容开始------------ SpringCloud Config 配置中心 Config 配置中心 Spring Cloud Config为分布式系统中的外部化配置提 ...

  9. Rocket - tilelink - AsyncCrossing

    https://mp.weixin.qq.com/s/v8plWCBD8vZkxykjJe4TCg   介绍AsyncCrossing的实现,主要介绍如何实现diplomacy Node和LazyMo ...

  10. 蒲公英 · JELLY技术周刊 Vol.09 StackOverflow - 2020 开发者年度报告

    登高远眺 沧海拾遗,积跬步以至千里 基础技术 StackOverFlow 2020 年开发者报告 技术问答社区 StackOverFlow 的年度报告,本次报告统计了来自于全球各地共 65000 名开 ...