【Netty】NIO 选择器 ( Selector ) 通道 ( Channel ) 缓冲区 ( Buffer ) 网络通信案例
I . NIO 通信 服务器端 流程说明
NIO 网络通信 服务器端 操作流程 , 与 BIO 原理类似 , 基本流程是 启动服务器套接字通道 , 创建选择器 , 将服务器套接字通道注册给选择器 , 监听客户端连接事件 , 客户端连接成功后 , 创建套接字通道 , 将新创建的通道注册给选择器 , 然后监听该通道的读取事件 ;
启动 -> 创建选择器 ->
创建服务器通道 -> 注册服务器通道 -> 监听连接 ->
创建客户端通道 -> 注册客户端通道 -> 监听数据读取/客户端连接
1 . 创建 服务器套接字通道 ( ServerSocketChannel ) :
① 创建通道 : 调用 ServerSocketChannel.open() 创建 , 创建后需要绑定本地端口号 , 需要获取 ServerSocket 用于绑定端口号 ;
② 获取服务器套接字 : 可以通过服务器套接字通道的 serverSocketChannel.socket() 方法获取 ServerSocket ;
③ 绑定本地端口号 : 调用 serverSocket.bind(new InetSocketAddress(8888)) 方法为该 ServerSocket 绑定 8888 端口号 ;
④ 设置非阻塞网络通信模式 : 并设置该通道网络通信模式为非阻塞模式 serverSocketChannel.configureBlocking(false) , 注意这里设置了非阻塞模式 , 其 对应的客户端套接字通道 SocketChannel 也要设置非阻塞模式 , 否则会报 IllegalBlockingModeException 异常 ;
2 . 创建选择器并注册通道 :
① 创建 选择器 ( Selector ) : 调用 Selector 的静态方法 open() , 即可创建一个 选择器 , Selector.open() ;
② 将 服务器套接字通道 注册给 选择器 ( Selector ) : serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT) , 监听通道的 SelectionKey.OP_ACCEPT 事件 , 如果有客户端连接服务器 , 就会触发该事件 , 生成相应的 SelectionKey , 并放入 选择器 ( Selector ) 的集合中 , 如果 选择器 ( Selector ) 正在调用 select 方法 ( 3 3 3 种 阻塞 / 非阻塞 监听方法 ) 监听 , 那么就会解除阻塞 ( 如果之前发生监听阻塞 ) , 返回触发事件的个数 ;
3 . 选择器 ( Selector ) 阻塞监听 : 这里调用 selector.select() 方法 , 阻塞监听 , 如果有事件发生 , 就会返回触发事件的个数 , 之后再遍历 SelectionKey 集合 , 依次处理触发的事件 ; 阻塞监听代码逻辑如下 :
while (true){
if(selector.select() <= 0){
continue;
}
//监听到触发事件, 处理对应的 SelectionKey 事件
}
- 1
- 2
- 3
- 4
- 5
- 6
4 . 处理客户端连接事件 :
① 判定 SelectionKey 的事件是否是连接事件 : 调用 key.isAcceptable() 方法 , 如果返回 true , 说明该事件是客户端连接事件 ;
② 创建 套接字通道 : 为该客户端创建一个对应的 SocketChannel 通道 , 调用 serverSocketChannel.accept() 方法 , 可以创建该客户端对应的 SocketChannel 通道 , 该方法是非阻塞的 , 因为该事件触发时已经知道有客户端连接 , 这里只是响应客户端的连接 ;
③ 设置非阻塞网络通信模式 : : sc.configureBlocking(false) 设置该通道是非阻塞通道 , 否则会报 IllegalBlockingModeException 异常 ;
④ 将通道注册给选择器 : 注册通道给选择器 , 并监听数据读取事件 , 同时设置通道对应的缓冲区 , 通道与客户端之间使用缓冲区进行交互 ; sc.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)) ;
5 . 处理数据读取事件 :
① 判定 SelectionKey 的事件是否是 读取 事件 : 调用 key.isReadable() 方法 , 如果返回 true , 说明该事件是 数据读取 事件 ;
② 获取通道 : 调用 SelectionKey 的 (SocketChannel) key.channel() 方法 , 获取该 SelectionKey 对应的通道 ;
③ 获取缓冲区 : 调用 (ByteBuffer) key.attachment() 获取对应的注册给 选择器 的缓冲区 ;
④ 读取缓冲区的数据 : 通道 socketChannel.read(byteBuffer) 方法 , 可以将数据读取数据到该缓冲区中 , 之后可以从缓冲区中获取数据 ;
II . NIO 通信 服务器端代码
服务器端代码 :
package kim.hsl.nio.demo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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;
public class Server {
public static void main(String[] args) {
try {
//I . 创建 ServerSocketChannel 监听 8888 端口
//创建 ServerSocketChannel, 等价于 BIO 中的 ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定本地端口, 获取其内部封装的 ServerSocket, 绑定 ServerSocket 的 8888 端口
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(8888));
//设置网络通信非阻塞模式
serverSocketChannel.configureBlocking(false);
//II . 创建选择器, 并注册监听事件
//获取 选择器 ( Selector )
Selector selector = Selector.open();
//将 serverSocketChannel 通道注册给 选择器 ( Selector ), 这里注册连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//III . 选择器监听
//选择器 ( Selector ) 开始监听
while (true){
//III . 1 . 判定事件触发 :
//阻塞监听, 查看是否有事件触发, 如果有就在下面处理
//如果没有 continue 终止循环, 继续下一次循环
System.out.println("服务器端开始阻塞监听 8888 端口事件");
if(selector.select() <= 0){
continue;
}
//当前状态说明 :
//如果能执行到该位置, 说明 selector.select(1000) 方法返回值大于 0
//当前有 1 个或多个事件触发, 下面就是处理事件的逻辑
//III . 2 . 处理事件集合 :
//获取当前发生的事件的 SelectionKey 集合, 通过 SelectionKey 可以获取对应的 通道
Set<SelectionKey> keys = selector.selectedKeys();
//使用迭代器迭代, 涉及到删除操作
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
//根据 SelectionKey 的事件类型, 处理对应通道的业务逻辑
//III . 2 . ( 1 ) 客户端连接服务器, 服务器端需要执行 accept 操作
if (key.isAcceptable()) {
System.out.println("服务器端 选择器 ( Selector ) 监听到客户端连接事件");
//创建通道 : 为该客户端创建一个对应的 SocketChannel 通道
//不等待 : 当前已经知道有客户端连接服务器, 因此不需要阻塞等待
//非阻塞方法 : ServerSocketChannel 的 accept() 是非阻塞的方法
SocketChannel sc = serverSocketChannel.accept();
//如果 ServerSocketChannel 是非阻塞的, 这里的 SocketChannel 也要设置成非阻塞的
//否则会报 java.nio.channels.IllegalBlockingModeException 异常
sc.configureBlocking(false);
//注册通道 : 将 SocketChannel 通道注册给 选择器 ( Selector )
//关注事件 : 关注事件时读取事件, 服务器端从该通道读取数据
//关联缓冲区 :
sc.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
//III . 2 . ( 2 ) 客户端写出数据到服务器端, 服务器端需要读取数据
if(key.isReadable()){
System.out.println("服务器端 选择器 ( Selector ) 监听到客户发送数据事件");
//获取 通道 ( Channel ) : 通过 SelectionKey 获取
SocketChannel socketChannel = (SocketChannel) key.channel();
//获取 缓冲区 ( Buffer ) : 获取到 通道 ( Channel ) 关联的 缓冲区 ( Buffer )
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
//读取客户端传输的数据 ;
socketChannel.read(byteBuffer);
System.out.println("客户端向服务器端发送数据 : " + new String(byteBuffer.array()));
}
//处理完毕后, 从 Set 集合中移除该 SelectionKey
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
III . NIO 通信 客户端 流程说明
NIO 网络通信 客户端 操作流程 : 首先创建客户端套接字通道 , 设置该通道为非阻塞通信模式 , 连接服务器的指定端口号 , 连接成功后 , 写出数据到服务器中 ;
创建套接字通道 -> 连接服务器 -> 写出数据到服务器
1 . 创建套接字通道 : 调用 SocketChannel.open() 方法 , 即可获取套接字通道 ( SocketChannel ) , 之后将该通道设置为 非阻塞通信模式 socketChannel.configureBlocking(false) ;
2 . 连接服务器 : 首先设置服务器的地址和端口号 new InetSocketAddress(“127.0.0.1”, 8888) , 然后调用 socketChannel 的 connect(address) 方法 , 即可连接服务器 , 该操作是非阻塞的操作 , 此时需要确保连接成功以后 , 再向服务器发送数据 ;
3 . 写出数据到服务器 : 先创建 缓冲区 Buffer , 将数据放入缓冲区 , ByteBuffer.wrap(“Hello World”.getBytes()) , 然后调用 套接字通道 ( socketChannel ) 的 write(buffer) 方法 , 将数据写出到服务器中 ;
IV . NIO 通信 客户端代码
NIO 通信 客户端代码 :
package kim.hsl.nio.demo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) {
try {
//1 . 客户端 SocketChannel : 先获取 SocketChannel, 相当于 BIO 中的 Socket, 设置非阻塞模式
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
//服务器地址 : 服务器的 IP 地址 和 端口号
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
//2 . 连接服务器 : 连接成功, 返回 true; 连接失败, 返回 false;
boolean isConnect = socketChannel.connect(address);
//没有连接成功
if (!isConnect) {
while (!socketChannel.finishConnect()){
System.out.println("等待连接成功");
}
}
//当前时刻状态分析 : 执行到该位置, 此时肯定是连接成功了
System.out.println("服务器连接成功");
//3 . 发送数据 : 如果连接成功 , 发送数据到服务器端
ByteBuffer buffer = ByteBuffer.wrap("Hello World".getBytes());
System.out.println("客户端向服务器端发送数据 \"Hello World\"");
socketChannel.write(buffer);
//目的是为了阻塞客户端, 不能让客户端退出
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
V . NIO 通信 示例运行
按照以下顺序操作
1 . 运行服务器端 : 服务器端运行后 , 选择器阻塞监听客户端的请求 , 主要是监听 客户端连接 和 数据读取 ( 服务器读取客户端发送的数据 ) 事件 ;
2 . 运行客户端 : 客户端运行后 , 连接服务器 , 然后向服务器写出 “Hello World” 字符串数据 ;
3 . 服务器端结果 : 服务器端监听到客户端连接 , 为客户端创建对应的通道 , 然后注册监听该通道的数据读取事件 , 之后继续监听客户端是否有数据写入 ;
文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。
原文链接:hanshuliang.blog.csdn.net/article/details/106392226
- 点赞
- 收藏
- 关注作者
评论(0)