一)Selector介绍
概述:用于使用单个线程处理多个通道。可以提高系统的效率。
初始化方法:
Selector selector = Selector.open();
事件类型:
第一种、SelectionKey.OP_CONNECT:连接事件
第二种、SelectionKey.OP_ACCEPT:接收事件
第三种、SelectionKey.OP_READ:读事件
第四种、SelectionKey.OP_WRITE:写事件
通道类型:
第一种、FileChannel:作用于IO文件流。
第二种、DatagramChannel:作用于UDP协议。
第三种、SocketChannel:作用于TCP协议。
第四种、ServerSocketChannel:作用于TCP协议。
备注:该章案例使用TCP协议。
打开服务器套接字方式:
// 创建一个通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 通道默认为阻塞模式,将通道设置为非阻塞模式
ssc.configureBlocking(false);
// 把端口绑定到通道上
ssc.socket().bind(new InetSocketAddress(8686));
// 为通道注册一个接收事件,当接收到事件时,selector.select()会返回一个key,否则会一直阻塞
ssc.register(selector, SelectionKey.OP_ACCEPT);
选择通道方式:
// selector.select()有数据时会返回一个key,否则会一直阻塞
selector.select();
通道key集合分类:
第一种、selectionKey.isConnectable(); // 连接
第二种、selectionKey.isAcceptable(); // 接收
第三种、selectionKey.isReadable(); // 读取
第四种、selectionKey.isWritable(); // 写入
通道集合源码片段:
// 将有数据的通道用set集合存储
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
// 拿到一个key的实例
SelectionKey key = iterator.next();
if (key.isConnectable()) { // 此实例是否已完成连接
System.out.println(key + "连接成功!");
} else if (key.isAcceptable()) { // 此实例是否已接收
} else if (key.isReadable()) { // 此实例是否已读取
} else if (key.isWritable() && key.isValid()) { // 此实例是否已写入
}
// 执行完之后,删除该key的实例,继续下一次循环
iterator.remove();
}
二)服务端
实现步骤:
第一步:创建一个Selector选择器,并open()。
第二步:创建一个ServerSocketChannel通道,并设置为非阻塞模式,并绑定一个端口。
第三步:把Selector选择器注册到ServerSocketChannel通道,并开启监听。
第四步:判断通道目前的状态,包括连接、接收、读取、写入等四种类型。再分别针对处理。
源码实现:
package com.oysept.nio.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
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;
/**
* 服务端
* @author ouyangjun
*/
public class Server {
// 端口
private final static int PORT = 8686;
// 缓冲区
private static ByteBuffer byteBuffer = ByteBuffer.allocate(10240);
/**
* 初始化服务端
*/
private void initServer() {
Selector selector = null;
ServerSocketChannel ssc = null;
try {
// 创建一个选择器
selector = Selector.open();
// 创建一个通道
ssc = ServerSocketChannel.open();
// 通道默认为阻塞模式,将通道设置为非阻塞模式
ssc.configureBlocking(false);
// 把端口绑定到通道上
ssc.socket().bind(new InetSocketAddress(PORT));
// 为通道注册一个接收事件,当接收到事件时,selector.select()会返回一个key,否则会一直阻塞
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 一直执行
while (true) {
// selector.select()有数据时会返回一个key,否则会一直阻塞
selector.select();
// 将有数据的通道用set集合存储
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
// 拿到一个key的实例
SelectionKey key = iterator.next();
if (key.isConnectable()) { // 此实例是否已完成连接
System.out.println(key + "连接成功!");
} else if (key.isAcceptable()) { // 此实例是否已接收
doAccept(key);
} else if (key.isReadable()) { // 此实例是否已读取
doRead(key);
} else if (key.isWritable() && key.isValid()) { // 此实例是否已写入
doWrite(key);
}
// 执行完之后,删除该key的实例,继续下一次循环
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 接收key之后的处理
* @param key
* @throws IOException
*/
public void doAccept(SelectionKey key) {
ServerSocketChannel ssc = null;
SocketChannel channel = null;
try {
// 返回此key的通道
ssc = (ServerSocketChannel) key.channel();
System.out.println("服务端一直循环监听ServerSocketChannel通道!");
// 监听通道
channel = ssc.accept();
// 设置非阻塞模式
channel.configureBlocking(false);
// 为通道注册一个读取事件
channel.register(key.selector(), SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取key之后的处理
* @param key
* @throws IOException
*/
public void doRead(SelectionKey key) {
SocketChannel channel = null;
try {
// 返回此key的通道
channel = (SocketChannel) key.channel();
// 把通道数据读取到缓冲区
int bytesRead = channel.read(byteBuffer);
while (bytesRead != -1) {
byteBuffer.flip();
System.out.println("从客户端发送过来的消息是: " + new String(byteBuffer.array(), "UTF-8"));
byteBuffer.clear();
bytesRead = channel.read(byteBuffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (channel != null) {
channel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 写入key之后的处理
* @param key
* @throws IOException
*/
public void doWrite(SelectionKey key) {
SocketChannel channel = null;
try {
// 返回此key的通道
channel = (SocketChannel) key.channel();
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
// 把缓冲区的数据写入到通道中
channel.write(byteBuffer);
}
// 对缓冲区进行压缩
byteBuffer.compact();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
Server server = new Server();
server.initServer(); // 启动服务端
}
}
三)客户端
实现步骤:同服务端类似。
源码实现:
package com.oysept.nio.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* 客户端
* @author ouyangjun
*/
public class Client {
// 端口
private final static int PORT = 8686;
// 缓冲区
private static ByteBuffer byteBuffer = ByteBuffer.allocate(10240);
/**
* 初始化客户端
* @throws IOException
*/
private void initClient() {
Selector selector = null;
SocketChannel channel = null;
try {
// 创建一个选择器
selector = Selector.open();
// 创建一个通道
channel = SocketChannel.open();
// 通道默认为阻塞模式,将通道设置为非阻塞模式
channel.configureBlocking(false);
// 将通道连接到端口上
channel.connect(new InetSocketAddress(PORT));
// 为通道注册一个连接事件
channel.register(selector, SelectionKey.OP_CONNECT);
// 一直执行
while (true) {
// selector.select()有数据时会返回一个key,否则会一直阻塞
selector.select();
// 将有数据的通道用set集合存储
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
// 拿到一个key的实例
SelectionKey key = iterator.next();
if (key.isConnectable()) {
doConnect(key);
} else if(key.isReadable()) {
doRead(key);
}
// 执行完之后,删除该key的实例,继续下一次循环
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 连接key之后的处理
* @param key
* @throws IOException
*/
public void doConnect(SelectionKey key) {
SocketChannel channel = null;
try {
// 返回此key的通道
channel = (SocketChannel) key.channel();
// 是否正在进行连接操作
if (channel.isConnectionPending()) {
// 完成连接通道
channel.finishConnect();
}
// 设置非阻塞模式
channel.configureBlocking(false);
// 发送消息到服务端
String msg = "你好,服务端!";
byteBuffer.clear();
byteBuffer.put(msg.getBytes("UTF-8"));
byteBuffer.flip();
channel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (channel != null) {
channel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 读取key之后的处理
* @param key
* @throws IOException
*/
public void doRead(SelectionKey key) {
SocketChannel channel = null;
try {
channel = (SocketChannel)key.channel();
channel.read(byteBuffer);
System.out.println("从服务端发送过来的消息是: " + new String(byteBuffer.array(), "UTF-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (channel != null) {
channel.close();
}
if (key.selector() != null) {
key.selector().close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
Client client = new Client();
client.initClient(); // 启动客户端
}
}
识别二维码关注个人微信公众号
本章完结,待续,欢迎转载!
本文说明:该文章属于原创,如需转载,请标明文章转载来源!