1.前言
此篇博文主要根据尚硅谷的视频课程把使用Netty需要了解的知识进行整理并根据自己的理解加以补充。由于内容较多所以将笔记分为多个部分,同时更新速度可能会比较慢,希望读者多包涵。
2.ServerSocketChannel 知识点补充
2.1基本介绍
- 一个面向流侦听的可选通道
- 通过调用此类的
open
方法创建一个新的通道 - 无法为任意预先存在的ServerSocket创建通道
- 一个新的通道被打开但还未绑定,就尝试调用
accept()
方法会抛NotYetBoundException
异常 - 通过调用当前类定义的bind的方法来绑定一个
server-socket channel
/**
* A selectable channel for stream-oriented listening sockets.
* <p> A server-socket channel is created by invoking the {@link #open() open}
* method of this class. It is not possible to create a channel for an arbitrary,
* pre-existing {@link ServerSocket}. A newly-created server-socket channel is
* open but not yet bound. An attempt to invoke the {@link #accept() accept}
* method of an unbound server-socket channel will cause a {@link NotYetBoundException}
* to be thrown. A server-socket channel can be bound by invoking one of the
* {@link #bind(java.net.SocketAddress,int) bind} methods defined by this class.
*
/
2.2配置套接字选项
使用setOption方法配置套接字选项
- 选项定义在StandardSocketOptions:
SO_RCVBUF // 套接字缓冲区大小
SO_REUSEADDR// 重复使用的地址
SO_BROADCAST // 广播传输
SO_KEEPALIVE // 连接保持活动状态
SO_LINGER // 延时时间
IP_TOS // Internet Protocol (IP)头中服务类型
IP_MULTICAST_IF // Internet协议(IP)多播数据报的网络接口 值类型是NetworkInterface
IP_MULTICAST_TTL // Internet协议(IP)多播数据报的网络接口,值类型为Integer范围0 <= value <= 255
IP_MULTICAST_LOOP // Internet协议(IP)多播数据报的环回,值类型Boolean
TCP_NODELAY // 禁用Nagel算法,值类型Boolean, TCP/IP协议使用Nagel合并短段提高网络效率
- 方法
public abstract <T> ServerSocketChannel setOption(SocketOption<T> name, T value)
throws IOException;
3.Selector
3.1基本介绍
- Java NIO可以处理一个或多个客户端连接,需要使用
Selector
选择器。 Selecttor
会检测注册中心通道是否有事件发生,如果有事件发生则对每个事件进行相应的处理。从而达到一个线程管理多个通道。- 只有连接的通道发生读写事件时才会进行读写,不需要每次建立线程,
减少了线程上下文切换
,从而减少了系统开销。
3.2.Selector 特点示意图
3.3.示意图说明:
- Netty 的 IO 线程 NioEventLoop 聚合了
Selector
(选择器,也叫多路复用器),可以同时并发处理成百上千个客户端连接。 - 当线程从某客户端
Socket 通道进行读写数据时
,若没有数据可用时,该线程可以进行其他任务。 - 线程通常将非阻塞 IO 的空闲时间用于在其他通道上
执行 IO 操作
,所以单独的线程可以管理多个输入和输出通道。 - 由于读写操作都是
非阻塞
的,这就可以充分提升 IO 线程的运行效率
,避免由于频繁 I/O 阻塞导致的线程挂起。 - 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,
架构的性能、弹性伸缩能力和可靠性
都得到了极大的提升。
3.4.Selector 方法
// 获得选择器对象
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
// 返回Selector 选定的key集合
public abstract Set<SelectionKey> selectedKeys();
// 如果没有事件发生则返回0
public abstract int selectNow() throws IOException;
// 设置超时timeout 毫秒数
public abstract int select(long timeout)throws IOException;
// 唤醒Selector
public abstract Selector wakeup();
3.5.Selector 注意事项
- ServerSocketChannel类似ServerSocket,SockertChannel类似Socket
- select()阻塞,selectNow()方法不阻塞,立即返回
- wakeup()唤醒Selector,同步
4.NIO 非阻塞网络编程
4.1.NIO非阻塞网络编程分析图
4.1.1.基本介绍
NIO非阻塞网络编程实际上是(ServerSocketChannel,SocketChannel,Selector,SelecttorKey)之间的关系图。
4.1.2.说明:
- 当客户端连接时,会通过 ServerSocketChannel 得到 SocketChannel。
- Selector 进行监听 select 方法,返回有事件发生的通道的个数。
- 将 socketChannel 注册到 Selector 上,register(Selector sel, int ops),一个 Selector 上可以注册多个 SocketChannel。
- 注册后返回一个 SelectionKey,会和该 Selector 关联(集合)。
- 进一步得到各个 SelectionKey(有事件发生)。
- 在通过 SelectionKey 反向获取 SocketChannel,方法 channel()。
- 可以通过得到的 channel,完成业务处理。
4.2.NIO非阻塞网络编程入门实例
4.2.1.基本介绍
1.创建一个服务端ServerSocketChannel01
2.创建一个客户端SocketChannelClient01
3.实例
// ServerSocketChannel01
public class ServerSocketChannel01 {
public static void main(String[] args) {
try {
// todo 目前发送消息有问题
// 1、创建
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2、获取Selector
Selector selector = Selector.open();
// 3、绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",8888));
// 4、设置非阻塞
serverSocketChannel.configureBlocking(false);
// 5、注册channel
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
// 6、验证是否有连接事件
if(selector.select(1000)==0){
System.out.println("服务器等待1秒,无连接");
continue;
}
// 7、处理其他事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.stream().iterator();
while (keyIterator.hasNext()){
SelectionKey next = keyIterator.next();
// 7.1 验证连接时间,接收,获得新的通道并把新通道注册到selector
if(next.isAcceptable()){
// 接收,获得新的通道
SocketChannel socketChannel = serverSocketChannel.accept();
if(Objects.nonNull(socketChannel)){
System.out.println("客户端连接"+socketChannel.getRemoteAddress());
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
}
// 7.2 读事件,重新写入buffer
if(next.isReadable()){
SocketChannel channel = (SocketChannel) next.channel();
// 获得Buffer
ByteBuffer buffer = (ByteBuffer) next.attachment();
channel.read(buffer);
System.out.println("服务端读取数据"+new String(buffer.array()));
}
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
// SocketChannelClient01
public static void main(String[] args) {
try {
// 1、创建
SocketChannel socketChannel = SocketChannel.open();
// 设置非阻塞
socketChannel.configureBlocking(false);
// 设置地址
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
// 验证是否连接上
boolean connect = socketChannel.connect(inetSocketAddress);
if(connect){
// 将数据写入channel
socketChannel.write(ByteBuffer.wrap("NIO测试".getBytes(StandardCharsets.UTF_8)));
//System.in.read();
}
} catch (IOException e) {
e.printStackTrace();
}
}
5.SelectionKey
5.1.基本介绍
- 每次向
Selector
注册通道时都会创建一个key。 - 一直保持密钥有效直到调用
cancel
方法关闭通道或者关闭选择器来取消密钥。 - 取消一个密钥不能立即从
Selector
中删除。而是把它添加到cancelled-key set
中,以便在下一次选择操作中将秘钥删除。 - 调用
isValid
方法测试秘钥有效性
5.2.SelectionKey的几种状态
// 读操作位置设置,值为1
public static final int OP_READ = 1 << 0;
// 写操作的操作设置位,值为4
public static final int OP_WRITE = 1 << 2;
// 用于套接字连接操作的操作设置位,值8
public static final int OP_CONNECT = 1 << 3;
// 用于套接字操作的操作设置位16
public static final int OP_ACCEPT = 1 << 4;
5.3.SelectionKey相关方法
// 返回为其创建密钥的通道
public abstract SelectableChannel channel();
// 获得选择器
public abstract Selector selector();
// 验证有效性
public abstract boolean isValid();
// 取消此密钥通道及其选择器的注册
public abstract void cancel();
// 是否可以只读
public final boolean isReadable();
// 是否可以只写
public final boolean isWritable();
// 测试此通道是否已完成或未能完成其套接字连接操作。
public final boolean isConnectable();
// 测试此密钥的通道是否准备好接受新套接字连接
public final boolean isAcceptable();
6.SocketChannel
6.1.基本介绍
SocketChannel,网络 IO 通道,具体负责进行读写操作。NIO 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。
相关方法如下
// 获取channle通道
public static SocketChannel open() throws IOException {
return SelectorProvider.provider().openSocketChannel();
}
// 返回标识此通道支持的操作的操作集
public final int validOps() {
return (SelectionKey.OP_READ| SelectionKey.OP_WRITE| SelectionKey.OP_CONNECT);
}
// 绑定地址
public abstract SocketChannel bind(SocketAddress local)throws IOException;
// 设置套接字值类型
public abstract <T> SocketChannel setOption(SocketOption<T> name, T value)throws IOException;
// 在不关闭通道的情况下关闭连接以进行读取
public abstract SocketChannel shutdownInput() throws IOException;
// 在不关闭通道的情况下关闭连接以进行写入。
public abstract SocketChannel shutdownOutput() throws IOException;
// 获得套接字
public abstract Socket socket();
// 验证channel是否连接
public abstract boolean isConnected();
// 判断此通道上的连接操作是否正在进行中
public abstract boolean isConnectionPending();
// 连接服务器
public abstract boolean connect(SocketAddress remote) throws IOException;
6.2.SelectableChannel相关方法
// 将channel注册到Selctor中
public abstract boolean isRegistered();
// 设置channel阻塞或非阻塞
public abstract SelectableChannel configureBlocking(boolean block)throws IOException;
7.笔记回顾
1.主要记录了ServerSocketChannel,SocketChannel,Selector,SelecttorKey之间关系
2.分别介绍了Selector,ServerSocketChannel,SocketChannel,SelecttorKey相关方法
3.分别介绍了Selector,ServerSocketChannel,SocketChannel,SelecttorKey的作用方法及特点
4.套接字选项的状态StandardSocketOptions
5.SelectionKey的几种状态