之前两篇文章写了关于NIO的博客,本来想一一口气写完这三篇的,但是由于前段时间项目特别赶,就把这篇给放下了,现在算是有些时间了,把第三篇写了。
前两篇一篇是写了Buffer、一篇写了Channel,下面写NIO的第三个很重要的部分Selector。
什么是Selector
在nio出现之前,也就是jdk1.4以前要实现网络通信的话使用socket进行,使用socket进行网络通信就会出现网络通信中IO阻塞的情况,显然这时候要让服务器面对很多连接同时进行会很吃力。NIO中的Selector就是针对这种情况推出的一种新的、非阻塞的IO方式。
常用概念
Selector有以下常用方法
- public static Selector open():打开一个选择器
- public abstract int select():选择一组Key,说明channel已经做好了IO的准备
- public abstract Set selectedKeys:返回已选中的Key
在进行阻塞式IO的时候是通过Socket获得IO流,在创建非阻塞的时候则是使用Selector创建上一篇写的Channel了。具体用到的类为ServerSocketChannel和SocketChannel。这两个类的父类是SelectableChannel,该类可以用来声明阻塞的模式,以及用来注册Selector的方法。
ServerSocketChannel的常用方法
- public final SelectableChannel configureBlocking(boolean block):这个方法用来配置阻塞模式,true为阻塞,false为非阻塞
- public final SelectionKey register(Selector sel, int ops):这个方法用来向选择器注册通道,并返回一个SelectionKey对象
- public static ServerSocketChannel open():这个方法用来打开一个服务器Chanel并返回
- public abstract ServerSocket socket():这个方法用来返回与通道相关的Socket
在Socket的网络通信方式中,我们使用的是accept()和connect()这样的方法来声明socket接收或者连接。Selector使用了不同的方式,它是在调用register()方法时,通过传递的第二个参数来判断进行哪种操作的,这第二个参数是int类型,可以把它叫做域,Selector域包含了一下几种:
- OP_ACCEPT:接收状态,相当于socket调用accept()方法
- OP_CONNECT:连接状态,相当于调用connect()方法
- OP_READ:进行读操作
- OP_WRITE:进行写操作
示例
下面写一个非阻塞的服务端实例来看一下
public static void main(String[] args) throws IOException {
int[] ports = { 8081, 8082, 8083, 8084, 8085 };// 列出一系列端口号,都可以用来接收请求
Selector selector = Selector.open();// 创建新的Selector
for (int i = 0; i < ports.length; i++) {
ServerSocketChannel ssc = null;// 声明ServerSocketChannel
ssc = ServerSocketChannel.open();// 通过ServerSocketChannel的open方法打开服务器套接字通道
ssc.configureBlocking(false);// 配置服务器为非阻塞状态
ServerSocket serverSocket = ssc.socket();// 检索与通道相关联的服务器套接字
InetSocketAddress inetSocketAddress = new InetSocketAddress(
"127.0.0.2", ports[i]);// 实例化绑定地址
serverSocket.bind(inetSocketAddress);// 套接字与网络地址进行绑定
ssc.register(selector, SelectionKey.OP_WRITE);// 注册选择器,类似Socket的accept()方法
System.out.println("服务器在" + ports[i]);
}
}
这样运行之后就可以看到控制台打印出来以下消息
服务器在8081
服务器在8082
服务器在8083
服务器在8084
服务器在8085
也就是说明在上面几个端口都在监听请求了。
服务器要想和客户端进行通信还需要用到另一个类SelectionKey,这个类可以用来判断连接状态以及发送消息。
它有以下常用方法:
- public abstract SelectableChannel channel():返回一个用来通信的Channel
- public final boolean isAcceptable():判断是否可接收新连接
- public final boolean isConnectable():判断是否连接已完成
- public final boolean isReadable():判断Channel是否已经处于可读状态
- public final boolean isWritable():判断Channel是否已经处于可写状态
直接使用代码来说明,再之前的方法里面加上以下代码
while (iter.hasNext()) {
SelectionKey key = iter.next();// 获得SelectionKey实例
if (key.isAcceptable()) {// 如果SelectionKey的状态处于接收状态
ServerSocketChannel server = (ServerSocketChannel) key
.channel();// 使用key的channel()方法获得服务器套接字通道
SocketChannel client = server.accept();// 通过调用ServerSocketChannel的accept()方法,获得套接字通道SocketChannel对象
client.configureBlocking(false);// 设置套接字通道为非阻塞形式
ByteBuffer outBuf = ByteBuffer.allocate(1024);// 为缓冲区分配大小为1024
outBuf.put(("现在是:" + new SimpleDateFormat("yyyy-MM-dd")
.format(new Date())).getBytes());// 将当前日期存放到缓冲区
outBuf.flip();// 重设缓冲区指针
client.write(outBuf);// 通过套接字通道将缓冲区的输入写入
client.close();// 关闭通道
}
}
这样再运行代码,当有请求发来时,就向请求的客户端发送一个当前的时间。再新建一个类,在main方法里写下如下代码来验证一下
public static void main(String[] args) throws UnknownHostException,
IOException {
Socket client = new Socket("127.0.0.2", 8082);
BufferedReader buf = null;
buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
String str = buf.readLine();
System.out.println("服务器端输出内容:" + str);
client.close();
buf.close();
}
控制台打印结果为
服务器端输出内容:现在是:2019-02-27
这样就证明创建的非阻塞服务器的可以使用了,并且进行相应的通信了。NIO的几个重要概念和使用方法也就介绍完了!