Selector 示意图和特点说明
一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型。架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
服务端流程
-
1、当客户端连接服务端时,服务端会通过 ServerSocketChannel 得到 SocketChannel:获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
-
2、切换非阻塞模式
ssChannel.configureBlocking(false);
-
3、绑定连接
ssChannel.bind(new InetSocketAddress(9999));
-
4、 获取选择器
Selector selector = Selector.open();
-
5、 将通道注册到选择器上, 并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
- 轮询式的获取选择器上已经“准备就绪”的事件
-
//轮询式的获取选择器上已经“准备就绪”的事件,大于0 说明存在 准备就绪的事件 while (selector.select() > 0) { System.out.println("轮一轮"); //7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)” Iterator it = selector.selectedKeys().iterator(); while (it.hasNext()) { //8. 获取准备“就绪”的是事件 SelectionKey sk = it.next(); //9. 判断具体是什么事件准备就绪 if (sk.isAcceptable()) { //10. 若“接收就绪”,获取客户端连接 SocketChannel sChannel = ssChannel.accept(); //11. 切换非阻塞模式 sChannel.configureBlocking(false); //12. 将该通道注册到选择器上并修改注册事件为read sChannel.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { //13. 获取当前选择器上“读就绪”状态的通道 SocketChannel sChannel = (SocketChannel) sk.channel(); //14. 读取数据 ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; while ((len = sChannel.read(buf)) > 0) { buf.flip(); System.out.println(new String(buf.array(), 0, len)); buf.clear(); } } //15. 处理完毕 移除选择键 SelectionKey it.remove(); } } }
客户端流程
1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
2.切换非阻塞模式
sChannel.configureBlocking(false);
3.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
4.发送数据给服务端
Scanner scan = new Scanner(System.in);
while(scan.hasNext()){
String message = scan.nextLine();
buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis())
+ "n" + message).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
//关闭通道
sChannel.close();
NIO非阻塞式网络通信入门案例
需求:服务端接收客户端的连接请求,并接收多个客户端发送过来的事件。
代码案例
/**
客户端
*/
public class Client {
public static void main(String[] args) throws Exception {
//1. 获取通道 - SelectionKey.OP_ACCEPT 对应监听接收事件
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
//2. 切换非阻塞模式
sChannel.configureBlocking(false);
//3. 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//4. 发送数据给服务端
Scanner scan = new Scanner(System.in);
while(scan.hasNext()){
String message = scan.nextLine();
buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis())
+ "n" + message).getBytes());
buf.flip();
//客户端写对应服务器端读就绪
sChannel.write(buf);
buf.clear();
}
//5. 关闭通道
sChannel.close();
}
}
/**
服务端
*/
public class Server {
public static void main(String[] args) throws IOException {
//1. 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2. 切换非阻塞模式
ssChannel.configureBlocking(false);
//3. 绑定连接
ssChannel.bind(new InetSocketAddress(9999));
//4. 获取选择器
Selector selector = Selector.open();
//5. 将通道注册到选择器上, 并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6. 轮询式的获取选择器上已经“准备就绪”的事件
while (selector.select() > 0) {
System.out.println("轮一轮");
//7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
//8. 获取准备“就绪”的是事件
SelectionKey sk = it.next();
//9. 判断具体是什么事件准备就绪
if (sk.isAcceptable()) {
//10. 若“接收就绪”,获取客户端连接
SocketChannel sChannel = ssChannel.accept();
//11. 切换非阻塞模式
sChannel.configureBlocking(false);
//12. 将该通道注册到选择器上 这里可以把缓存指定上
sChannel.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
//13. 获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel) sk.channel();
//14. 读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while ((len = sChannel.read(buf)) > 0) {
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
}
//15. 取消选择键 SelectionKey
it.remove();
}
}
}
}
尚硅谷代码案例
package com.atguigu.nio;
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;
//网络服务器端程序
public class NIOServer {
public static void main(String[] args) throws Exception{
//1. 得到一个ServerSocketChannel对象
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//2. 得到一个Selector对象
Selector selector=Selector.open();
//3. 绑定一个端口号, 在服务器的6666监听 2个方式有什么区别
//serverSocketChannel.bind(new InetSocketAddress(6666));
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//4. 设置非阻塞方式
serverSocketChannel.configureBlocking(false);
//5. 把ServerSocketChannel对象注册给Selector对象
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6. 干活
while(true){
//6.1 监控客户端
//如果使用 selector.select() 就会阻塞在这里的
if(selector.select(1000)==0){ //nio非阻塞式的优势
System.out.println("Server:等待了1秒,无客户端连接");
continue;
}
//6.2 得到SelectionKey,判断通道里的事件
Iterator keyIterator=selector.selectedKeys().iterator();
while(keyIterator.hasNext()){
SelectionKey key=keyIterator.next();
if(key.isAcceptable()){ //客户端连接请求事件
SocketChannel socketChannel=serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){ //读取客户端数据事件
SocketChannel channel=(SocketChannel) key.channel();
ByteBuffer buffer=(ByteBuffer) key.attachment();
channel.read(buffer);
System.out.println("接收到客户端数据:"+new String(buffer.array()));
}
// 6.3 手动从集合中移除当前key,防止重复处理
keyIterator.remove();
}
}
}
}
package com.atguigu.nio;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws Exception{
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//提供服务器端的ip 和 端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if (!socketChannel.connect(inetSocketAddress)) {
while (!socketChannel.finishConnect()) {
System.out.println("因为连接需要时间,客户端不会阻塞,可以做其它工作..");
}
}
//...如果连接成功,就发送数据
String str = "hello, 尚硅谷~";
//Wraps a byte array into a buffer
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
// 发送数据,将 buffer 数据写入 channel
socketChannel.write(buffer);
System.in.read();
}
}
使用6666端口有个坑爹的地方,端口号被占用。
解决:www.cnblogs.com/jf-67/p/842…
原因:blog.csdn.net/hi_pig2003/…