DAY20 Channel(通道)NIO(同步,非阻塞)、Selector(选择器)、NIO2-AIO(异步、非阻塞) - 指南
学习目标
- 能够使用ServerSocketChannel和SocketChannel实现连接并收发信息
同步非阻塞连接(NIO)
ServlerSocketChannel和SocketChannel收发信息
- 能够说出Selector选择器的作用
使用了多路复用,只需要一个线程就可以处理多个通道,
降低内存占用率,减少CPU切换时间,在高并发、高频段业务环境下有非常重要的优势
- 能够使用Selector选择器
选择器Selector_多路信息接收
- 能够说出AIO的特点
异步非阻塞IO
非阻塞:
服务器(accept)不用等待客户端请求,可以继续做其他的事情
客户端connect不会等待连接服务器成功,可以继续做其他的事情
异步:
不用轮询获取监听客户端,有客户端请求服务器,会触发回调函数(CompletionHandler),来处理这个请求
第一章 Channel(通道)NIO(同步,非阻塞)
1.ServerSocketChannel和SocketChannel创建连接
同步阻塞实现
package com.itheima.demo01TCPNIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/*
实现同步阻塞的服务器
相关的类:
java.nio.channels.ServerSocketChannel:针对面向流的侦听套接字的可选择通道。
获取对象的方式:
static ServerSocketChannel open() 打开服务器套接字通道。
成员方法:
ServerSocketChannel bind(SocketAddress local) 给服务器绑定指定的端口号
SocketChannel accept() 监听客户端的请求
SelectableChannel configureBlocking(boolean block) 设置服务器的阻塞模式 true:阻塞 false:非阻塞
实现步骤:
1.使用open方法获取ServerSocketChannel对象
2.使用ServerSocketChannel对象中的方法bind给服务器绑定指定的端口号
3.使用ServerSocketChannel对象中的方法accept监听客户端的请求
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.使用open方法获取ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind给服务器绑定指定的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//3.使用ServerSocketChannel对象中的方法accept监听客户端的请求
System.out.println("服务器等待客户端的连接...");
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("有客户端连接服务器...");
}
}
package com.itheima.demo01TCPNIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/*
实现同步阻塞的客户端
相关的类:
java.nio.channels.SocketChannel:用于面向流的连接插座的可选通道。
获取对象的方法:
static SocketChannel open() 打开套接字通道。
成员方法:
boolean connect(SocketAddress remote) 根据服务器的ip地址和端口号连接服务器
参数:
SocketAddress remote:封装服务器IP地址和端口号的对象,直接new
返回值:boolean
连接服务器成功:true
连接服务器失败:false
SelectableChannel configureBlocking(boolean block) 设置客户端的阻塞模式 true:阻塞(不写默认) false:非阻塞
实现步骤:
1.使用open方法获取客户端ScoketChannel对象
2.使用ScoketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
注意:
服务器没有启动会抛出连接异常ConnectException: Connection refused: connect
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.使用open方法获取客户端ScoketChannel对象
SocketChannel socketChannel = SocketChannel.open();
//2.使用ScoketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
}
}
服务器同步非阻塞实现
package com.itheima.demo02TCPNIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/*
服务器实现同步非阻塞
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.使用open方法获取ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind给服务器绑定指定的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//SelectableChannel configureBlocking(boolean block) 设置服务器的阻塞模式 true:阻塞(不写默认) false:非阻塞
serverSocketChannel.configureBlocking(false);
//3.使用ServerSocketChannel对象中的方法accept监听客户端的请求
System.out.println("服务器等待客户端的连接...");
SocketChannel socketChannel = serverSocketChannel.accept();//accept阻塞:等待客户端连接 非阻塞:不会等待直接执行后续代码
System.out.println("有客户端连接服务器...");
}
}
服务器轮询监听客户端
package com.itheima.demo03TCPNIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/*
服务器实现同步非阻塞
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用open方法获取ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind给服务器绑定指定的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//SelectableChannel configureBlocking(boolean block) 设置服务器的阻塞模式 true:阻塞(不写默认) false:非阻塞
serverSocketChannel.configureBlocking(false);
//轮询监听客户端的请求==>死循环一直执行
while (true){
//3.使用ServerSocketChannel对象中的方法accept监听客户端的请求
System.out.println("服务器等待客户端的连接...");
SocketChannel socketChannel = serverSocketChannel.accept();//accept阻塞:等待客户端连接 非阻塞:不会等待直接执行后续代码
//对客户端SocketChannel进行一个非空判断,没有客户端连接服务器,accept方法返回null
if(socketChannel!=null){
System.out.println("有客户端连接服务器...");
break;//结束轮询
}else{
System.out.println("没有客户端连接服务器,休息2秒钟,干点其他事情,在继续轮询获取客户端连接...");
Thread.sleep(2000);
}
}
}
}
执行结果:
服务器等待客户端的连接...
没有客户端连接服务器,休息2秒钟,干点其他事情,在继续轮询获取客户端连接...
服务器等待客户端的连接...
没有客户端连接服务器,休息2秒钟,干点其他事情,在继续轮询获取客户端连接...
服务器等待客户端的连接...
没有客户端连接服务器,休息2秒钟,干点其他事情,在继续轮询获取客户端连接...
服务器等待客户端的连接...
没有客户端连接服务器,休息2秒钟,干点其他事情,在继续轮询获取客户端连接...
服务器等待客户端的连接...
没有客户端连接服务器,休息2秒钟,干点其他事情,在继续轮询获取客户端连接...
服务器等待客户端的连接...
有客户端连接服务器...
客户端轮询连接服务器:
package com.itheima.demo03TCPNIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/*
SelectableChannel configureBlocking(boolean block) 设置客户端的阻塞模式
true:阻塞(不写默认)
false:非阻塞
boolean connect(SocketAddress remote) 根据服务器的ip地址和端口号连接服务器
1.客户端设置为阻塞模式:connect方法会多次尝试连接服务器
连接成功connect方法返回true
连接服务器失败,会抛出连接异常
2.客户端设置为非阻塞模式:connect只会连接一次服务器
connect方法无论连接成功还是失败都返回false
所以客户端设置为非阻塞模式没有意义
*/
public class TCPClient {
public static void main(String[] args) throws IOException, InterruptedException {
//客户端轮询连接服务器,连接成功,结束轮询
while (true){
try {
//1.使用open方法获取客户端ScoketChannel对象,创建socketChannel对象代码需要放在try-catch中,否则会连接服务器失败
SocketChannel socketChannel = SocketChannel.open();
//socketChannel.configureBlocking(true);//true:阻塞(不写默认)
//socketChannel.configureBlocking(false);//false:非阻塞
//2.使用ScoketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
System.out.println("客户端开始连接服务器....");
boolean b = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
System.out.println(b);
System.out.println("客户端连接服务器成功,结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端connect连接服务器失败,休息2秒钟,干点其他事情...");
Thread.sleep(2000);
}
}
}
}
客户端开始连接服务器....
客户端connect连接服务器失败,休息2秒钟,干点其他事情...
客户端开始连接服务器....
客户端connect连接服务器失败,休息2秒钟,干点其他事情...
客户端开始连接服务器....
客户端connect连接服务器失败,休息2秒钟,干点其他事情...
客户端开始连接服务器....
true
客户端连接服务器成功,结束轮询...
2.ServlerSocketChannel和SocketChannel收发信息
package com.itheima.demo04TCPNIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/*
客户端轮询连接服务器成功:给服务器发送数据,读取服务器回写的数据
SocketChannel中的成员方法:
int write(ByteBuffer src) 给服务器发送数据
int read(ByteBuffer dst) 读取服务器回写的数据
*/
public class TCPClient {
public static void main(String[] args) throws IOException, InterruptedException {
//客户端轮询连接服务器,连接成功,结束轮询
while (true){
try {
//1.使用open方法获取客户端ScoketChannel对象
SocketChannel socketChannel = SocketChannel.open();
//2.使用ScoketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
System.out.println("客户端开始连接服务器....");
boolean b = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
System.out.println(b);
System.out.println("客户端连接服务器成功,给服务器发送数据,读取服务器回写的数据...");
//int write(ByteBuffer src) 给服务器发送数据
ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
System.out.println("容量:"+buffer.capacity());
System.out.println("索引:"+buffer.position());
System.out.println("限定:"+buffer.limit());
socketChannel.write(buffer);
//int read(ByteBuffer dst) 读取服务器回写的数据
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
int len = socketChannel.read(buffer2);
//System.out.println("客户端读取到服务器发送的数据:"+new String(buffer2.array(),0,len));
buffer2.flip();//缩小limit的范围 position=0 limit=position(读取的有效字节个数)
System.out.println("客户端读取到服务器发送的数据:"+new String(buffer2.array(),0,buffer2.limit()));
socketChannel.close();
System.out.println("客户端读写完数据结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端connect连接服务器失败,休息2秒钟,干点其他事情...");
Thread.sleep(2000);
}
}
}
}
package com.itheima.demo04TCPNIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/*
服务器实现同步非阻塞
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用open方法获取ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind给服务器绑定指定的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//SelectableChannel configureBlocking(boolean block) 设置服务器的阻塞模式 true:阻塞(不写默认) false:非阻塞
serverSocketChannel.configureBlocking(false);
//轮询监听客户端的请求==>死循环一直执行
while (true){
//3.使用ServerSocketChannel对象中的方法accept监听客户端的请求
System.out.println("服务器等待客户端的连接...");
SocketChannel socketChannel = serverSocketChannel.accept();//accept阻塞:等待客户端连接 非阻塞:不会等待直接执行后续代码
//对客户端SocketChannel进行一个非空判断,没有客户端连接服务器,accept方法返回null
if(socketChannel!=null){
System.out.println("有客户端连接服务器,服务器读取客户端发送的数据,给客户端回写数据...");
//int read(ByteBuffer dst) 读取客户端发送的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socketChannel.read(buffer);
String msg = new String(buffer.array(), 0, len);
System.out.println("服务器读取客户端发送的数据:"+msg);
//int write(ByteBuffer src) 给客户端发送数据
socketChannel.write(ByteBuffer.wrap("收到,谢谢".getBytes()));
System.out.println("服务器读写数据完成,结束轮询...");
break;//结束轮询
}else{
System.out.println("没有客户端连接服务器,休息2秒钟,干点其他事情,在继续轮询获取客户端连接...");
Thread.sleep(2000);
}
}
//释放资源
serverSocketChannel.close();
}
}
第二章 Selector(选择器)
1.多路复用的概念
选择器Selector是NIO中的重要技术之一。它与SelectableChannel联合使用实现了非阻塞的多路复用。使用它可以节省CPU资源,提高程序的运行效率。
"多路"是指:服务器端同时监听多个“端口”的情况。每个端口都要监听多个客户端的连接。
- 服务器端的非多路复用效果

如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造成系统性能下降。
- 服务器端的多路复用效果

使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、高频段业务环境下有非常重要的优势
2.选择器Selector_服务器端实现多路注册
package com.itheima.demo05Selector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
/*
选择器Selector_服务器端实现多路注册
java.nio.channels.Selector:SelectableChannel对象的多路复用器。
获取Selector对象的方式:
static Selector open() 打开选择器。
注册Channel(服务器通道)到Selector上:ServerSocketChannel中的方法
SelectionKey register(Selector sel, int ops) 使用给定的选择器注册此频道,返回一个选择键。
参数:
Selector sel:传递要注册的选择器对象
int ops:传递对应的事件
使用SelectionKey中的常量:SelectionKey.OP_ACCEPT(固定的写法,把服务器通道注册到选择器)
实现步骤:
1.创建3个ServerSocketChannel服务器对象
2.分别给3个ServerSocketChannel服务器对象绑定不同的端口号
3.设置3个ServerSocketChannel对象为非阻塞模式(想使用Selector注册服务器通道,必须是非阻塞)
4.获取Selector对象
5.使用服务器ServerSocketChannel对象中的方法register,把3个服务器通道注册到Selector选择器上
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.创建3个ServerSocketChannel服务器对象
ServerSocketChannel channel01 = ServerSocketChannel.open();
ServerSocketChannel channel02 = ServerSocketChannel.open();
ServerSocketChannel channel03 = ServerSocketChannel.open();
//2.分别给3个ServerSocketChannel服务器对象绑定不同的端口号
channel01.bind(new InetSocketAddress(7777));
channel02.bind(new InetSocketAddress(8888));
channel03.bind(new InetSocketAddress(9999));
//3.设置3个ServerSocketChannel对象为非阻塞模式(想使用Selector注册服务器通道,必须是非阻塞)
channel01.configureBlocking(false);
channel02.configureBlocking(false);
channel03.configureBlocking(false);
//4.获取Selector对象
Selector selector = Selector.open();
//5.使用服务器ServerSocketChannel对象中的方法register,把3个服务器通道注册到Selector选择器上
channel01.register(selector, SelectionKey.OP_ACCEPT);
channel02.register(selector, SelectionKey.OP_ACCEPT);
channel03.register(selector, SelectionKey.OP_ACCEPT);
}
}
3.选择器Selector_常用方法
- Selector的keys()方法
- 此方法返回一个Set集合,表示:已注册通道的集合。每个已注册通道封装为一个SelectionKey对象。
- Selector的selectedKeys()方法
- 此方法返回一个Set集合,表示:当前已连接的通道的集合。每个已连接通道同一封装为一个SelectionKey对象。
- Selector的select()方法
- 此方法会阻塞,直到至少有1个客户端连接。
- 此方法会返回一个int值,表示有几个客户端连接了服务器。

package com.itheima.demo06Selector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Set;
/*
选择器Selector_常用方法
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建3个ServerSocketChannel服务器对象
ServerSocketChannel channel01 = ServerSocketChannel.open();
ServerSocketChannel channel02 = ServerSocketChannel.open();
ServerSocketChannel channel03 = ServerSocketChannel.open();
//2.分别给3个ServerSocketChannel服务器对象绑定不同的端口号
channel01.bind(new InetSocketAddress(7777));
channel02.bind(new InetSocketAddress(8888));
channel03.bind(new InetSocketAddress(9999));
//3.设置3个ServerSocketChannel对象为非阻塞模式(想使用Selector注册服务器通道,必须是非阻塞)
channel01.configureBlocking(false);
channel02.configureBlocking(false);
channel03.configureBlocking(false);
//4.获取Selector对象
Selector selector = Selector.open();
//5.使用服务器ServerSocketChannel对象中的方法register,把3个服务器通道注册到Selector选择器上
channel01.register(selector, SelectionKey.OP_ACCEPT);
channel02.register(selector, SelectionKey.OP_ACCEPT);
channel03.register(selector, SelectionKey.OP_ACCEPT);
//Selector的keys()方法:已经注册的通道的集合
Set<SelectionKey> keys = selector.keys();
System.out.println("已经注册的通道的数量:"+keys.size());//3
//服务器轮询监听客户端的请求
while (true){
//Selector的select()方法:获取客户端连接的数量,没有客户端连接服务器,此方法会一直阻塞
int select = selector.select();
System.out.println("连接服务器的客户端数量:"+select);
//Selector的selectedKeys()方法:获取当前已经连接的通道的集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("已经连接服务器的通道的数量:"+selectionKeys.size());
//获取完一个客户端的连接,睡眠2秒,在进行下一次轮询
Thread.sleep(2000);
}
}
}
package com.itheima.demo06Selector;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/*
开启三个线程,每个线程分别创建一个客户端对象,连接服务器的三个端口
*/
public class TCPClinet {
public static void main(String[] args) {
new Thread(()->{
//创建客户端对象,轮询请求服务器
while (true){
try(SocketChannel socketChannel = SocketChannel.open()){
System.out.println("客户端开始连接7777端口...");
socketChannel.connect(new InetSocketAddress("127.0.0.1",7777));
System.out.println("客户端连接7777端口成功,结束轮询...");
break;
}catch (Exception e){
System.out.println("客户端连接7777端口异常");
}
}
}).start();
new Thread(()->{
//创建客户端对象,轮询请求服务器
while (true){
try(SocketChannel socketChannel = SocketChannel.open()){
System.out.println("客户端开始连接8888端口...");
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
System.out.println("客户端连接8888端口成功,结束轮询...");
break;
}catch (Exception e){
System.out.println("客户端连接8888端口异常");
}
}
}).start();
new Thread(()->{
//创建客户端对象,轮询请求服务器
while (true){
try(SocketChannel socketChannel = SocketChannel.open()){
System.out.println("客户端开始连接9999端口...");
socketChannel.connect(new InetSocketAddress("127.0.0.1",9999));
System.out.println("客户端连接9999端口成功,结束轮询...");
break;
}catch (Exception e){
System.out.println("客户端连接9999端口异常");
}
}
}).start();
}
}
程序执行结果:
已经注册的通道的数量:3
连接服务器的客户端数量:1
已经连接服务器的通道的数量:1
连接服务器的客户端数量:2
已经连接服务器的通道的数量:3
连接服务器的客户端数量:0
已经连接服务器的通道的数量:3
4.选择器Selector_多路信息接收
package com.itheima.demo07Selector;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/*
开启三个线程,每个线程分别创建一个客户端对象,连接服务器的三个端口
*/
public class TCPClinet {
public static void main(String[] args) {
new Thread(()->{
//创建客户端对象,轮询请求服务器
while (true){
try(SocketChannel socketChannel = SocketChannel.open()){
System.out.println("客户端开始连接7777端口...");
socketChannel.connect(new InetSocketAddress("127.0.0.1",7777));
//给服务器发送数据
socketChannel.write(ByteBuffer.wrap("你好服务器,我是连接7777端口号的客户端!".getBytes()));
System.out.println("客户端连接7777端口成功,结束轮询...");
break;
}catch (Exception e){
System.out.println("客户端连接7777端口异常");
}
}
}).start();
new Thread(()->{
//创建客户端对象,轮询请求服务器
while (true){
try(SocketChannel socketChannel = SocketChannel.open()){
System.out.println("客户端开始连接8888端口...");
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
System.out.println("客户端连接8888端口成功,结束轮询...");
//给服务器发送数据
socketChannel.write(ByteBuffer.wrap("你好服务器,我是连接8888端口号的客户端!".getBytes()));
break;
}catch (Exception e){
System.out.println("客户端连接8888端口异常");
}
}
}).start();
new Thread(()->{
//创建客户端对象,轮询请求服务器
while (true){
try(SocketChannel socketChannel = SocketChannel.open()){
System.out.println("客户端开始连接9999端口...");
socketChannel.connect(new InetSocketAddress("127.0.0.1",9999));
//给服务器发送数据
socketChannel.write(ByteBuffer.wrap("你好服务器,我是连接9999端口号的客户端!".getBytes()));
System.out.println("客户端连接9999端口成功,结束轮询...");
break;
}catch (Exception e){
System.out.println("客户端连接9999端口异常");
}
}
}).start();
}
}
package com.itheima.demo07Selector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/*
选择器Selector_常用方法
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建3个ServerSocketChannel服务器对象
ServerSocketChannel channel01 = ServerSocketChannel.open();
ServerSocketChannel channel02 = ServerSocketChannel.open();
ServerSocketChannel channel03 = ServerSocketChannel.open();
//2.分别给3个ServerSocketChannel服务器对象绑定不同的端口号
channel01.bind(new InetSocketAddress(7777));
channel02.bind(new InetSocketAddress(8888));
channel03.bind(new InetSocketAddress(9999));
//3.设置3个ServerSocketChannel对象为非阻塞模式(想使用Selector注册服务器通道,必须是非阻塞)
channel01.configureBlocking(false);
channel02.configureBlocking(false);
channel03.configureBlocking(false);
//4.获取Selector对象
Selector selector = Selector.open();
//5.使用服务器ServerSocketChannel对象中的方法register,把3个服务器通道注册到Selector选择器上
channel01.register(selector, SelectionKey.OP_ACCEPT);
channel02.register(selector, SelectionKey.OP_ACCEPT);
channel03.register(selector, SelectionKey.OP_ACCEPT);
//Selector的keys()方法:已经注册的通道的集合
Set<SelectionKey> keys = selector.keys();
System.out.println("已经注册的通道的数量:"+keys.size());//3
//服务器轮询监听客户端的请求
while (true){
//Selector的select()方法:获取客户端连接的数量,没有客户端连接服务器,此方法会一直阻塞
int select = selector.select();
System.out.println("连接服务器的客户端数量:"+select);
//Selector的selectedKeys()方法:获取当前已经连接的通道的集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("已经连接服务器的通道的数量:"+selectionKeys.size());
//处理Selector监听客户端的请求的事件:遍历Set集合,获取每一个SelectionKey对象
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()){
SelectionKey selectionKey = it.next();
//获取SelectionKey里边封装的ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
System.out.println("获取当前通道ServerSocketChannel监听的端口号:"+serverSocketChannel.getLocalAddress());
//处理监听的accept事件==>获取请求服务器的SocketChannel对象
SocketChannel socketChannel = serverSocketChannel.accept();
//读取客户端SocketChannel发送的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socketChannel.read(buffer);
System.out.println("服务器读取到客户端发送的数据:"+new String(buffer.array(),0,len));
//处理完SelectionKey监听到的事件,要在Set集合中移除已经处理完的SelectionKey对象
it.remove();//使用迭代器对象移除集合中的元素,不会抛出并发修改异常
}
//获取完一个客户端的连接,睡眠2秒,在进行下一次轮询
Thread.sleep(2000);
}
}
}
第三章 NIO2-AIO(异步、非阻塞)
1.AIO概述
JDK7新增的:AsynchronousIO:异步、非阻塞IO
2.AIO异步非阻塞连接
package com.itheima.demo08TCPAIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/*
创建AIO的服务器端:
java.nio.channels.AsynchronousServerSocketChannel:用于面向流的侦听套接字的异步通道。
获取对象的方法:
AsynchronousServerSocketChannel open() 打开异步服务器套接字通道。
成员方法:
AsynchronousServerSocketChannel bind(SocketAddress local) 给服务器绑定指定的端口号
void accept(A attachment, CompletionHandler<?> handler) 监听客户端的请求,默认就是非阻塞的方法
参数:
A attachment:附件,可以传递null
CompletionHandler<?> handler:事件处理的接口,用于处理accept方法监听到的事件
CompletionHandler:也叫回调函数,客户端请求服务器之后,会自动执行CompletionHandler接口中的方法
java.nio.channels.CompletionHandler接口:用于消除异步I / O操作结果的处理程序。
void completed(V result, A attachment) 客户端连服务器成功执行的方法
void failed(Throwable exc, A attachment) 客户端连接服务器失败执行的方法
---------------------------------------------------------------
实现步骤:
1.创建异步非阻塞的服务器AsynchronousServerSocketChannel对象
2.使用bind方法给AsynchronousServerSocketChannel对象绑定执行的端口号
3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端的请求
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建异步非阻塞的服务器AsynchronousServerSocketChannel对象
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
//2.使用bind方法给AsynchronousServerSocketChannel对象绑定执行的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端的请求
System.out.println("accept方法开始执行了...");
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("客户端连接服务器成功...");
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接服务器失败...");
}
});
System.out.println("accept方法执行结束了...");
//accept是一个非阻塞的方法,我们执行完accept方法,可以去做其他的事情
//死循环目的是不让程序停止,当有客户端请求服务器,会自动执行回调函数
while (true){
System.out.println("正在忙其他的事情!");
Thread.sleep(2000);
}
}
}
package com.itheima.demo08TCPAIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousSocketChannel;
/*
创建AIO的客户端
java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
获取对象的方法:
AsynchronousSocketChannel open() 打开异步套接字通道。
成员方法:
Future<Void> connect(SocketAddress remote) 连接服务器的方法,参数传递服务器的IP地址和端口号
-----------------------------------------
实现步骤:
1.创建异步非阻塞的客户端AsynchronousSocketChannel对象
2.使用AsynchronousSocketChannel对象中的方法connect连接服务器
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.创建异步非阻塞的客户端AsynchronousSocketChannel对象
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//2.使用AsynchronousSocketChannel对象中的方法connect连接服务器
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
}
}
3. AIO异步连接:异步阻塞读写
服务器读取客户端read方法:阻塞的会一直等待客户端发送数据
package com.itheima.demo09TCPAIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.Future;
/*
AsynchronousSocketChannel成员方法:
Future<Integer> write(ByteBuffer src) 给服务器发送数据
Future<Integer> read(ByteBuffer dst) 读取服务器发送的数据
Future<Void> connect(SocketAddress remote) 连接服务器的方法,参数传递服务器的IP地址和端口号
java.util.concurrent.Future<Void>接口
boolean isDone() 如果此任务完成,则返回 true 。
true:连接服务器成功
false:连接服务器正在进行中(还没有连接上服务器)
注意:
connect是一个非阻塞的方法,不会等待方法运行完毕,连接服务器成功在执行下边的代码
客户端连接服务器需要时间的,如果没有连接成功,就给服务器发送数据,会抛出异常
*/
public class TCPClient {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建异步非阻塞的客户端AsynchronousSocketChannel对象
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//2.使用AsynchronousSocketChannel对象中的方法connect连接服务器
Future<Void> future = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
System.out.println(future.isDone());//false:还未连接上服务器
System.out.println("1111");
//休眠5秒钟,等待客户端连接服务器成功,在给服务器发送数据
Thread.sleep(5000);
System.out.println(future.isDone());//true:已经连接服务器成功
//给服务器发送数据
if(future.isDone()){
socketChannel.write(ByteBuffer.wrap("你好服务器".getBytes()));
}
//释放资源
socketChannel.close();
}
}
package com.itheima.demo09TCPAIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/*
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建异步非阻塞的服务器AsynchronousServerSocketChannel对象
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
//2.使用bind方法给AsynchronousServerSocketChannel对象绑定执行的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端的请求
System.out.println("accept方法开始执行了...");
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("客户端连接服务器成功...");
//获取客户端发送的数据 Future<Integer> read(ByteBuffer dst)
ByteBuffer buffer = ByteBuffer.allocate(1024);
//read方法是一个阻塞的方法,会一直等待客户端发送数据
Future<Integer> future = result.read(buffer);
Integer len = 0;
try {
len = future.get();//获取客户端发送的数据长度
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("服务器读取到客户端发送的数据:"+new String(buffer.array(),0,len));
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接服务器失败...");
}
});
System.out.println("accept方法执行结束了...");
//accept是一个非阻塞的方法,我们执行完accept方法,可以去做其他的事情
//死循环目的是不让程序停止,当有客户端请求服务器,会自动执行回调函数
while (true){
System.out.println("正在忙其他的事情!");
Thread.sleep(10000);
}
}
}
4.AIO异步连接:异步非阻塞读写
服务器读取客户端read方法:可以设置等待客户端发送数据的时间,到时间之后,无论客户端是否发送数据,都会执行对应的方法
package com.itheima.demo10TCPAIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/*
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建异步非阻塞的服务器AsynchronousServerSocketChannel对象
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
//2.使用bind方法给AsynchronousServerSocketChannel对象绑定执行的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端的请求
System.out.println("accept方法开始执行了...");
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("客户端连接服务器成功...");
/*
void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<?> handler) 是一个非阻塞的读取方法
参数
ByteBuffer dst - 要传输字节的缓冲区,用来存储读取到的数据
long timeout - 完成I / O操作的最长时间
TimeUnit unit - timeout参数的时间单位 (TimeUnit.SECONDS:秒)
A attachment - 要附加到I / O操作的对象; 可以是null
CompletionHandler handler - 消费结果的处理程序.是一个回调函数
客户端给服务器write发送数据之后,服务器会自动的执行这个回调函数
void completed(V result, A attachment) 服务器读取客户端发送数据成功,执行的方法
void failed(Throwable exc, A attachment) 服务器读取客户端发送数据失败,执行的方法
*/
ByteBuffer buffer = ByteBuffer.allocate(1024);
result.read(buffer, 10, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
//客户端10秒钟之内给服务器发送数据,执行completed成功的方法
@Override
public void completed(Integer result, Object attachment) {
System.out.println("服务器读取客户端发送数据成功,执行的方法");
buffer.flip();//缩小limit范围
String msg = new String(buffer.array(), 0, buffer.limit());//把ByteBuffer有效的字节转换为字符串
System.out.println("服务器读取客户端发送的信息为:"+msg);
}
//客户端10秒钟之内没有给服务器发送数据,执行failed失败的方法
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("服务器读取客户端发送数据失败,执行的方法");
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接服务器失败...");
}
});
System.out.println("accept方法执行结束了...");
//accept是一个非阻塞的方法,我们执行完accept方法,可以去做其他的事情
//死循环目的是不让程序停止,当有客户端请求服务器,会自动执行回调函数
while (true){
System.out.println("正在忙其他的事情!");
Thread.sleep(10000);
}
}
}
package com.itheima.demo10TCPAIO;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.Future;
/*
AsynchronousSocketChannel成员方法:
Future<Integer> write(ByteBuffer src) 给服务器发送数据
Future<Integer> read(ByteBuffer dst) 读取服务器发送的数据
Future<Void> connect(SocketAddress remote) 连接服务器的方法,参数传递服务器的IP地址和端口号
java.util.concurrent.Future<Void>接口
boolean isDone() 如果此任务完成,则返回 true 。
true:连接服务器成功
false:连接服务器正在进行中(还没有连接上服务器)
注意:
connect是一个非阻塞的方法,不会等待方法运行完毕,连接服务器成功在执行下边的代码
客户端连接服务器需要时间的,如果没有连接成功,就给服务器发送数据,会抛出异常
*/
public class TCPClient {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建异步非阻塞的客户端AsynchronousSocketChannel对象
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//2.使用AsynchronousSocketChannel对象中的方法connect连接服务器
Future<Void> future = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
System.out.println(future.isDone());//false:还未连接上服务器
System.out.println("1111");
//休眠5秒钟,等待客户端连接服务器成功,在给服务器发送数据
Thread.sleep(15000);
System.out.println(future.isDone());//true:已经连接服务器成功
//给服务器发送数据
if(future.isDone()){
socketChannel.write(ByteBuffer.wrap("你好服务器".getBytes()));
}
//释放资源
socketChannel.close();
}
}
执行结果:
accept方法开始执行了...
accept方法执行结束了...
正在忙其他的事情!
客户端连接服务器成功...
正在忙其他的事情!
服务器读取客户端发送数据失败,执行的方法
正在忙其他的事情!
正在忙其他的事情!
正在忙其他的事情!
正在忙其他的事情!
第四章 NIO与AIO自我总结
NIO与AIO自我总结
NIO是JDK4出现的,AIO是JDK7出现的
NIO是同步非阻塞,AIO是异步非阻塞
对于NIO来说,非阻塞指服务器端的accept方法,同步指的是必须轮询监听客户端,主动获取客户端请求
NIO同步非堵塞代码体现
while (true){
//3.使用ServerSocketChannel对象中的方法accept监听客户端的请求
System.out.println("服务器等待客户端的连接...");
//accept阻塞:等待客户端连接 非阻塞:不会等待直接执行后续代码
SocketChannel socketChannel = serverSocketChannel.accept();
//对客户端SocketChannel进行一个非空判断,没有客户端连接服务器,accept方法返回null
if(socketChannel!=null){
System.out.println("有客户端连接服务器...");
break;//结束轮询
}else{
System.out.println("没有客户端连接服务器,休息2秒钟,干点其他事情,在继续轮询获取客户端连接...");
Thread.sleep(2000);
}
}
对AIO来说,非阻塞服务器端指的是accept方法与read方法,
异步指的是不需要轮询监听客户端,等待客户端连接,然后会有回调函数处理请求
AIO异步非堵塞代码体现
System.out.println("accept方法开始执行了...");
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("客户端连接服务器成功...");
/*
void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<?> handler) 是一个非阻塞的读取方法
参数
ByteBuffer dst - 要传输字节的缓冲区,用来存储读取到的数据
long timeout - 完成I / O操作的最长时间
TimeUnit unit - timeout参数的时间单位 (TimeUnit.SECONDS:秒)
A attachment - 要附加到I / O操作的对象; 可以是null
CompletionHandler handler - 消费结果的处理程序.是一个回调函数
客户端给服务器write发送数据之后,服务器会自动的执行这个回调函数
void completed(V result, A attachment) 服务器读取客户端发送数据成功,执行的方法
void failed(Throwable exc, A attachment) 服务器读取客户端发送数据失败,执行的方法
*/
ByteBuffer buffer = ByteBuffer.allocate(1024);
result.read(buffer, 10, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
//客户端10秒钟之内给服务器发送数据,执行completed成功的方法
@Override
public void completed(Integer result, Object attachment) {
System.out.println("服务器读取客户端发送数据成功,执行的方法");
buffer.flip();//缩小limit范围
String msg = new String(buffer.array(), 0, buffer.limit());//把ByteBuffer有效的字节转换为字符串
System.out.println("服务器读取客户端发送的信息为:"+msg);
}
//客户端10秒钟之内没有给服务器发送数据,执行failed失败的方法
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("服务器读取客户端发送数据失败,执行的方法");
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接服务器失败...");
}
});
System.out.println("accept方法执行结束了...");
//accept是一个非阻塞的方法,我们执行完accept方法,可以去做其他的事情
//死循环目的是不让程序停止,当有客户端请求服务器,会自动执行回调函数
while (true){
System.out.println("正在忙其他的事情!");
Thread.sleep(10000);
}
浙公网安备 33010602011771号