一 selector
1.1 介绍
-
java的NIO,用非阻塞的IO方式,可以用一个线程,处理多个的客户端连接,就会使用到selector(选择器)。
-
netty的IO线程NIOEventLoop聚合了selector(选择器,也叫多路复用器),可以同时并发处理成百上千各个客户端连接。
-
当线程从某客户端socket通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。
-
只有在连接/通道真正有读写事件发生时,才会进行读写,就大大地减少了系统的开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。避免了多线程之间的上下文切换导致系统的开销。
-
一个I/O线程可以并发处理N个哭护短连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
1.2 常用方法
selector.select();//阻塞
selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回
selector.wakeup();//唤醒 selector
selector.selectNow();//不阻塞,立即返回
1.3 selectionKey
selectionKey:表示selector和网络通道的注册关系。
int OP_ACCEPT 有新的网络连接可以accept ,值为16
int OP_CONNECT 代表连接已经建立,值为8
int OP_READ 代表读操作,值为1
int OP_WRITE 代表写操作,值为4
1.4 ServerSocketChannel
serversocketchannel:作用是在服务器端监听新的客户端socket连接。
1.5 socketchannel
网络IO通道,具体负责进行读写操作。NIO把缓冲区的数据写入通道,或者把通道里的数据读到缓存区。
1.6 selector和socketchannel和selectorkey的关系
1.当客户端连接时,会通过ServerSocketChannel得到socketchannel
2.selector进行监听select方法,返回有事件发生的通道的个数;
3.将socketchannel注册到selector上,一个selector上可以注册多个socketchannel
4.注册后返回一个selectorkey,会和该selector关联。
二 代码实操
2.1 使用Nio的非网络阻塞机制,实现客户端和网络端进行通讯。
1 代码
package com.ljf.netty.nio.cs;
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;
/**
* @ClassName: NioServer
* @Description: TODO
* @Author: liujianfu
* @Date: 2022/05/15 11:40:54
* @Version: V1.0
**/
public class NioServer {
public static void main(String[] args) throws IOException {
//创建ServerSocketCHannel-> ServerSocket
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//得到一个selector对象
Selector selector=Selector.open();
//绑定一个端口6666,在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把serverSocketChannel注册到selector关心的事件为OP_Accept
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待客户端的连接
while(true){
//这里我们等待设置1秒,如果没有事件发生,则返回
//selector.select()>0.则就获取到相关的selectionKey集合,也表示已经获取到关注的事件集合
//System.out.println("selector.select():"+selector.select());
if(selector.select(1000)==0){
System.out.println("服务器等待1秒,无连接");
continue;
}
//2.selector.selectedKeys() 返回关注事件的集合
Set<SelectionKey> selectionKeys=selector.selectedKeys();
//3.进行遍历,获取可用
Iterator<SelectionKey> keyIterator=selectionKeys.iterator();
while(keyIterator.hasNext()){
//获取到selectionKey
SelectionKey key=keyIterator.next();
//根据key对应的通道发生相应的事件,做出相应事件处理
if (key.isAcceptable()){//如果是OP_ACCEPT,则又新客户端连接
//该客户端生成一个socketchannel
SocketChannel socketChannel=serverSocketChannel.accept();
System.out.println("客户端连接成功,生成了一个 socketChannel"+socketChannel.hashCode());
//将 socketChannel设置为非阻塞
socketChannel.configureBlocking(false);
//将socketchannel注册到selector,关于事件为OP_READ,同时给socketchannel
//关联一个buffer
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){//开始op_read
//通过key,反向获取到对应的channel
SocketChannel channel=(SocketChannel) key.channel();
//获取到该channel关联的buffer
ByteBuffer buffer=(ByteBuffer) key.attachment();
channel.read(buffer);
System.out.println("from 客户端:"+new String(buffer.array()));
}
//手动从集合移动当前selectionKey,防止重复操作
keyIterator.remove();
System.out.println("========================");
}
}
}
}
客户端:
package com.ljf.netty.nio.cs;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @ClassName: NioClient
* @Description: TODO
* @Author: liujianfu
* @Date: 2022/05/15 11:40:45
* @Version: V1.0
**/
public class NioClient {
public static void main(String[] args) throws IOException {
//得到一个网络通道
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("连接客户端需要一些时间,因为是非阻塞io,暂时可以先做其他工作!");
}
}
//连接成功
String string="hello,北京";
ByteBuffer buffer=ByteBuffer.wrap(string.getBytes());
//返送数据,将buffer中的数据写入到channel
socketChannel.write(buffer);
System.in.read();
}
}
3.结果
启动一个服务端,两个客户端