Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

  

  传统的Socket IO,服务端为每个客户端连接开启一个线程来处理,这样服务器可以支持的客户端连接数是有限的。当需要处理大量的客户端连接,而每个客户端连接的数据量又不是很大时,使用Socket NIO来实现是可行的。 

  Socket NIO的特点是使用了Selector,使用一个线程处理多个连接的请求,向Selector中注册感兴趣的动作,Selector会在动作发生时通知接收端。

  Socket NIO较Socket IO可以建立更多的连接。tomcat对NIO的支持可以看看。

  

  下面的例子是客户端从服务器端下载文件,客户端使用了多线程技术模拟同时下载。Selector可以同时处理多个客户端的连接事件并通知服务器端进行响应操作。

  服务器端的代码为:  

package nio;

import java.io.File;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable; //模拟下载服务
public class DownloadServer<T> implements Callable<T>{
//A selector may be created by invoking the open method of this class, which will use the system's default selector provider to create a new selector.
//A selector may also be created by invoking the openSelector method of a custom selector provider.
//A selector remains open until it is closed via its close method.
//A selectable channel's registration with a selector is represented by a SelectionKey object.
private Selector selector;//创建全局selector
private Map<SocketChannel, Handle> map = new HashMap<SocketChannel, Handle>();//socketChannel和handle之间的映射 //创建一个服务器serverSocketChannel,并与selector进行注册
public DownloadServer() throws Exception {
selector = Selector.open();
//ServerSocketChannel is a selectable channel for stream-oriented listening sockets.
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//if false then it will be placed non-blocking mode
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(2361));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //服务端接收客户端连接事件
} //对selector.select进行迭代,并依次进行处理
public T call() throws Exception {
System.out.println("startTo listen in 2361....");
for(; ;) {
//Selects a set of keys whose corresponding channels are ready for I/O operations
//select() performs a blocking selection operation. It returns only after at least one channel is selected
selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while(keyIterator.hasNext()) {
//A selectionkey is a token representing the registration of a SelectableChannel with a Selector.
//A selectionkey is created each time a channel is registered with a selector.
SelectionKey key = keyIterator.next();
if(key.isValid())
handle(key);
keyIterator.remove();
}
}
} //处理每个key,对于acceptable的key,由主类进行处理,而其他事件,则由内部类进行处理
private void handle(final SelectionKey key) throws Exception {
//Tests whether this key's channel is ready to accept a new socket connection.
if(key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
//Accepts a connection made to this channel's socket.
SocketChannel socketChannel = channel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);//注册读事件
map.put(socketChannel, new Handle());//把socket和handle进行绑定
}
//用map中的handle处理read和write事件,以模拟多个文件同时进行下载
if(key.isReadable() || key.isWritable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
final Handle handle = map.get(socketChannel);
if(handle != null)
handle.handle(key);
}
} //内部类,模拟一个内部类处理一个文件下载服务,多个类可以处理多个文件下载服务
private class Handle{
private StringBuilder message;
private boolean writeOK = true;
private ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
private FileChannel fileChannel;
private String fileName; private void handle(SelectionKey key) throws Exception {
if(key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
if(writeOK)
message = new StringBuilder();
while(true) {
byteBuffer.clear();
int r = socketChannel.read(byteBuffer);
if(r == 0)
break;
if(r == -1) {
socketChannel.close();
key.cancel();
return;
}
message.append(new String(byteBuffer.array(), 0, r));
}
//将接收到的信息转化成文件名,以映射到服务器上的指定文件
if(writeOK && invokeMessage(message)) {
socketChannel.register(selector, SelectionKey.OP_WRITE);
writeOK = false;
}
}
//向客户端写数据
if(key.isWritable()) { //读方法中的socketChannel和写方法中的socketChannel应该是一个,不需要flip吗?
if(!key.isValid())
return;
SocketChannel socketChannel = (SocketChannel) key.channel();
if(fileChannel == null)
fileChannel = new FileInputStream(fileName).getChannel();
byteBuffer.clear();
int w = fileChannel.read(byteBuffer);
//如果文件已写完,则关掉key和socket
if(w <= 0) {
fileName = null;
fileChannel.close();
fileChannel = null;
writeOK = true;
socketChannel.close();
key.channel();
return;
}
byteBuffer.flip();
socketChannel.write(byteBuffer);
}
} //将信息转化成文件名
private boolean invokeMessage(StringBuilder message) {
String m = message.toString();
try {
File f = new File(m);
if(!f.exists())
return false;
fileName = m;
return true;
} catch(Exception e) {
return false;
}
} } public static void main(String[] args) throws Exception {
/*
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new DownloadServer<Object>());
executorService.shutdown();
*/
new DownloadServer().call();
}
}

  客户端的代码为:

package nio;

import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class SelectorClient<T> implements Callable<T>{
private FileChannel fileChannel;
private static Selector selector;
private ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
private String serverFileName;//服务器上的文件
private String localFileName;//下载到客户端的文件名 public SelectorClient(String serverFileName, String localFileName) {
this.serverFileName = serverFileName;
this.localFileName = localFileName;
} public T call() throws Exception {
//开启selector,并建立socket到指定端口的连接
if(selector == null)
selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("172.16.72.181", 2361));
channel.register(selector, SelectionKey.OP_CONNECT); //客户端连接服务端事件
//进行信息读取
for(; ;) {
selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
//连接事件
if(key.isConnectable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
if(socketChannel.isConnectionPending())
socketChannel.finishConnect();
socketChannel.write(ByteBuffer.wrap(serverFileName.getBytes()));//向服务器发信息,信息中即服务器上的文件名
socketChannel.register(selector, SelectionKey.OP_READ); // 读事件
}
//读事件
if(key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
byteBuffer.clear();
if(!socketChannel.isConnected())
return null;
//向本机下载文件创建文件channel
if(fileChannel == null)
fileChannel = new RandomAccessFile(localFileName, "rw").getChannel();
int r = socketChannel.read(byteBuffer);
//如果文件下载完毕,则关掉channel,同时关掉socketChannel
if(r <= 0) {
if(fileChannel != null)
fileChannel.close();
channel.close();
key.cancel();
return null;
}
byteBuffer.flip();
//写到下载文件中
fileChannel.write(byteBuffer);
}
}
}
} //客户端用10个线程向服务器端下载文件,并保存为不同的文件
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i = 0; i < 10; i++) {
executorService.submit(new SelectorClient<Object>("test.txt", "d:/down" + i + ".txt"));
}
executorService.shutdown();
}
}

NIO基础篇(二)的更多相关文章

  1. php基础篇-二维数组排序 array_multisort

    原文:php基础篇-二维数组排序 array_multisort 对2维数组或者多维数组排序是常见的问题,在php中我们有个专门的多维数组排序函数,下面简单介绍下: array_multisort(a ...

  2. NIO相关基础篇二

    转载请注明原创出处,谢谢! 上篇NIO相关基础篇一,主要介绍了一些基本的概念以及缓冲区(Buffer)和通道(Channel),本篇继续NIO相关话题内容,主要就是文件锁.以及比较关键的Selecto ...

  3. JavaScript笔记基础篇(二)

    基础篇主要是总结一些工作中遇到的技术问题是如何解决的,应为本人属于刚入行阶段技术并非大神如果笔记中有哪些错误,或者自己的一些想法希望大家多多交流互相学习. 1.ToFixed()函数 今天在做Birt ...

  4. Qt入门之基础篇 ( 二 ) :Qt项目建立、编译、运行和发布过程解析

    转载请注明出处:CN_Simo. 题解: 本篇内容主讲Qt应用从创建到发布的整个过程,旨在帮助读者能够快速走进Qt的世界. 本来计划是讲解Qt源码静态编译,如此的话读者可能并不能清楚地知道为何要静态编 ...

  5. docker+k8s基础篇二

    Docker+K8s基础篇(二) docker的资源控制 A:docker的资源限制 Kubernetes的基础篇 A:DevOps的介绍 B:Kubernetes的架构概述 C:Kubernetes ...

  6. Python基础篇(二)_基本数据类型

    Python基础篇——基本数据类型 数字类型:整数类型.浮点数类型.复数类型 整数类型:4种进制表示形式:十进制.二进制.八进制.十六进制,默认采用十进制,其他进制需要增加引导符号 进制种类 引导符号 ...

  7. node基础篇二:模块、路由、全局变量课堂(持续)

    今天继续更新node基础篇,今天主要内容是模块.路由和全局变量. 模块这个概念,在很多语言中都有,现在模块开发已经成为了一种潮流,它能够帮助我们节省很多的时间,当然咱们的node自然也不能缺少,看下例 ...

  8. Hybrid APP基础篇(二)->Native、Hybrid、React Native、Web App方案的分析比较

    说明 Native.Hybrid.React.Web App方案的分析比较 目录 前言 参考来源 前置技术要求 楔子 几种APP开发模式 概述 Native App Web App Hybrid Ap ...

  9. C语言----输入输出语句(基础篇二)

    今天整理一下自己的基础篇输入和输出的理解,自己没有研究系统输入和输出函数,以后有时间在去深究,之前在别人的博客里面看到这么一句话分享给大家,“学习就是一个不断抄袭,模仿,练习和创新的一个过程”. 使用 ...

随机推荐

  1. hihoCoder #1078 : 线段树的区间修改(线段树区间更新板子题)

    #1078 : 线段树的区间修改 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 对于小Ho表现出的对线段树的理解,小Hi表示挺满意的,但是满意就够了么?于是小Hi将问题 ...

  2. Educational Codeforces Round 21(A.暴力,B.前缀和,C.贪心)

    A. Lucky Year time limit per test:1 second memory limit per test:256 megabytes input:standard input ...

  3. Codeforces Round #416 (Div. 2)(A,思维题,暴力,B,思维题,暴力)

    A. Vladik and Courtesy time limit per test:2 seconds memory limit per test:256 megabytes input:stand ...

  4. Eclipse集成Tomcat的步骤,我已测试N次都是成功的

    本文转自:https://www.cnblogs.com/weixing/p/3229983.html#undefined 使用Eclipse开发B/S结构Web应用时,必须使用Web应用服务器,常见 ...

  5. 获取屏幕宽高度与可视区域宽高度(availWidth、clientWidth、width、innerWidth)

    经常会遇到需要获取屏幕宽度.高度,可视区域宽度.高度等问题,也就常跟这几个打交道,一不小心,还真爱弄混淆了. 先来列举下这几个吧: screen.availHeight.screen.availWid ...

  6. HDU 4763 Theme Section

    题目: It's time for music! A lot of popular musicians are invited to join us in the music festival. Ea ...

  7. 从零开始学习前端开发 — 12、CSS3弹性布局

    一.分栏布局 1.设置栏数column-count:数值; 2.设置每栏的宽度column-width:数值+单位; 注:当设置了column-width,column-count会失效,二者设置其一 ...

  8. 如何用SVN版本控制器将提交的文件还原到以前的版本

    工具/原料 SVN乌龟软件和相关的文件 方法/步骤 在相关的文件中右击鼠标,按右图进行选择 在弹出框的地方点击我标记的地方,查看下曾经提交过的版本文件 在弹出框的地方,上面就是有版本号,下面就是我们文 ...

  9. Linux初学者必知的5个学习网站

    分享几个Linux初学者一定要知道的5个学习网站 工具/原料 有一颗学习Linux的心 电脑 方法/步骤 1 推荐一:鸟哥的Linux私房菜(http://vbird.dic.ksu.edu.tw/) ...

  10. LinkedList 源码分析(JDK 1.8)

    1.概述 LinkedList 是 Java 集合框架中一个重要的实现,其底层采用的双向链表结构.和 ArrayList 一样,LinkedList 也支持空值和重复值.由于 LinkedList 基 ...