一、通道选择器
通道注册
:需要使用Selector管理通道,然后将就绪的通道封装成SelectionKey对象。
- 设置通道为非阻塞 ServerSocketChannel/SocketChannel#configureBlocking(false)
- 注册通道ServerSocketChannel/SocketChannel#register(selector,事件类型[,附件信息])
NIO的网络编程的思想是基于异步事件处理,底层通过Selector去管理注册列表,一旦注册列表的相关通道就绪,selector就会将就绪的通道放置在事件处理队列中,用户可以通过Selector#selectedKeys()
获取就绪的keys
所有就绪的key只能被处理一次,因此用户必须在处理完事件key后,将该事件在事件处理列表中移除。
事件取消注册
:通道关闭
,key#cancel()
ServerSocketChannel ssc=ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(9999));
//设置通道非阻塞
ssc.configureBlocking(false);
//创建通道选择器
Selector selector=Selector.open();
//注册ACCEPT事件类型 转发
ssc.register(selector,SelectionKey.OP_ACCEPT);
//迭代遍历事件key
while(true){
//返回需要处理的事件个数,如果没有该方法会阻塞,也有可能直接返回0(当程序调用Selector#wakeup)
int num = selector.select();
if(num >0){
//事件处理
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key = keys.next();
//处理对应的事件key
if(key.isAcceptable()){
//处理转发事件
ServerSocketChannel channel= (ServerSocketChannel) key.channel();
SocketChannel s=channel.accept();//立即返回一个不为null的SocketChannel
s.configureBlocking(false);//注册读
s.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
//处理读事件
SocketChannel s= (SocketChannel) key.channel();
//处理读
...
//注册写
s.register(selector,SelectionKey.OP_WRITE[,请求参数]);
}else if(key.isWritable()){
//处理写事件
SocketChannel s= (SocketChannel) key.channel();
//根据请求参数给出响应
...
s.shutdownOutput();//告知写结束
s.close();
}
//移除key
keys.remove();
}
}
}
二、NIO单线程版本
public class NIOBootstrapServer {
public static void main(String[] args) throws IOException {
//1、创建ServerSocket
ServerSocketChannel ssc=ServerSocketChannel.open();
//2、绑定监听端口
ssc.bind(new InetSocketAddress(9999));
//3、设置通道非阻塞
ssc.configureBlocking(false);
//4、创建通道选择器
Selector selector= Selector.open(); //nio多是open来注册东西
//5、注册ACCEPT事件类型 转发
ssc.register(selector,SelectionKey.OP_ACCEPT);
//迭代遍历事件key
while(true){
//返回需要处理的事件个数,如果没有该方法会阻塞,也有可能直接返回0(当程序调用Selector#wakeup)
System.out.println("尝试选择待处理的keys...");
//可以出来keys的数目,如果没有该方法block
int num = selector.select();
if(num >0){
//事件处理
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key = keys.next();
//处理对应的事件key
if(key.isAcceptable()){
System.out.println("处理转发同时注册读...");
//处理转发事件
ServerSocketChannel channel= (ServerSocketChannel) key.channel();
SocketChannel s=channel.accept();//立即返回一个不为null的SocketChannel
s.configureBlocking(false);//设置非阻塞
//注册读
s.register(selector,SelectionKey.OP_READ,new ByteArrayOutputStream());
}else if(key.isReadable()){
System.out.println("处理读...");
//处理读事件
SocketChannel s= (SocketChannel) key.channel(); //拿到注册过的SocketChannel
//处理读
ByteBuffer buffer=ByteBuffer.allocate(1024);
ByteArrayOutputStream baos= (ByteArrayOutputStream) key.attachment();
//一次尝试读取一个缓冲区
int n=s.read(buffer);
if(n==-1){
System.out.println("服务器收到:"+new String(baos.toByteArray()));
//根据请求参数给出响应
ByteArrayInputStream bais=new ByteArrayInputStream((new Date().toLocaleString()).getBytes());
//注册写
s.register(selector, SelectionKey.OP_WRITE,bais);
}else{
buffer.flip();
baos.write(buffer.array(),0,n);
}
}else if(key.isWritable()){
System.out.println("处理写...");
//处理写事件
SocketChannel s= (SocketChannel) key.channel();
ByteArrayInputStream bais = (ByteArrayInputStream)key.attachment();
byte[] bytes=new byte[1024];
int n = bais.read(bytes);
if(n==-1){
s.shutdownOutput();//告知写结束
s.close(); //关闭通道
}else{
s.write(ByteBuffer.wrap(bytes,0,n));
}
}
//移除key
keys.remove();
}
}
}
}
}
三、NIO多线程版【了解】
依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
完整实现如下
public class NIOBootstrapServerPool {
//该线程池主要负责请求的转发
private static ExecutorService master= Executors.newFixedThreadPool(66);
//该线程池主要负责请求的响应
private static ExecutorService worker= Executors.newFixedThreadPool(66);
//注册转发队列
private static final AtomicBoolean NEED_REG_DISPATH= new AtomicBoolean(false);
//注册读队列
private static final List<ChannelAndAtt> READ_QUEUE= new Vector<ChannelAndAtt>();
//注册写队列
private static final CopyOnWriteArrayList<ChannelAndAtt> WRITE_QUEUE= new CopyOnWriteArrayList<ChannelAndAtt>();
public static void main(String[] args) throws IOException {
//创建serverSocket
ServerSocketChannel ssc=ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(9999));
//设置通道非阻塞
ssc.configureBlocking(false);
//创建通道选择器selector
Selector selector= Selector.open();
//注册ACCEPT事件类型 转发
ssc.register(selector,SelectionKey.OP_ACCEPT);
//迭代遍历事件key
while(true){
//返回需要处理的事件个数,如果没有该方法会阻塞,也有可能直接返回0(当程序调用Selector#wakeup)
// System.out.println("尝试选择待处理的keys...");
int num = selector.select(1);
if(num >0){
//事件处理
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key = keys.next();
//处理对应的事件key
if(key.isAcceptable()){ //请求转发
key.cancel();//取消转发注册
master.submit(new ProcessDispatcher(key,selector));
}else if(key.isReadable()){ //读取IO处理
key.cancel();//取消读注册
worker.submit(new ProcessRead(key,selector));
}else if(key.isWritable()){ //响应IO处理
key.cancel();//取消写注册
worker.submit(new ProcessWrite(key,selector));
}
//删除当前事件key,删除并不意味着取消注册
keys.remove();
}
}else {
if(NEED_REG_DISPATH.get()){//需要重新注册ACCEPT
System.out.println("重新注册ACCEPT");
ssc.register(selector,SelectionKey.OP_ACCEPT);
NEED_REG_DISPATH.set(false);
}
while(READ_QUEUE.size()>0){
ChannelAndAtt channelAndAtt = READ_QUEUE.remove(0);
//注册读
System.out.println("注册READ");
channelAndAtt.getChannel().register(selector,SelectionKey.OP_READ,channelAndAtt.att);
}
while(WRITE_QUEUE.size()>0){
ChannelAndAtt channelAndAtt = WRITE_QUEUE.remove(0);
//注册写
System.out.println("注册写");
channelAndAtt.getChannel().register(selector,SelectionKey.OP_WRITE,channelAndAtt.att);
}
}
}
}
/**
* 处理请求写
*/
public static class ProcessWrite implements Runnable{
private SelectionKey key;
private Selector selector;
public ProcessWrite(SelectionKey key, Selector selector) {
this.key = key;
this.selector = selector;
}
@Override
public void run() {
try {
SocketChannel s= (SocketChannel) key.channel();
ByteArrayInputStream bais = (ByteArrayInputStream)key.attachment();
byte[] bytes=new byte[1024];
int n = bais.read(bytes);//最多从bais获取一个缓冲区的数据
if(n==-1){
s.shutdownOutput();//告知写结束
s.close(); //关闭通道
}else{
//最多写一个缓冲区的数据
s.write(ByteBuffer.wrap(bytes,0,n));
//恢复写注册
WRITE_QUEUE.add(new ChannelAndAtt(s,bais));
}
//打破main线程
selector.wakeup();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 处理请求读
*/
public static class ProcessRead implements Runnable{
private SelectionKey key;
private Selector selector;
public ProcessRead(SelectionKey key, Selector selector) {
this.key = key;
this.selector = selector;
}
@Override
public void run() {
try {
//处理读事件
SocketChannel s= (SocketChannel) key.channel();
//处理读
ByteBuffer buffer=ByteBuffer.allocate(1024);
ByteArrayOutputStream baos= (ByteArrayOutputStream) key.attachment();
int n=s.read(buffer);
if(n==-1){
//根据请求参数给出响应
Object req= SerializationUtils.deserialize(baos.toByteArray());
System.out.println("服务器收到:"+req);
ByteArrayInputStream bais=new ByteArrayInputStream(SerializationUtils.serialize(new Date()));
//注册写
WRITE_QUEUE.add(new ChannelAndAtt(s,bais));
}else{
buffer.flip();
baos.write(buffer.array(),0,n);
//恢复读注册
READ_QUEUE.add(new ChannelAndAtt(s,baos));
}
//打断mian线程阻塞
selector.wakeup();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 处理请求转发
*/
public static class ProcessDispatcher implements Runnable{
private SelectionKey key;
private Selector selector;
public ProcessDispatcher(SelectionKey key, Selector selector) {
this.key = key;
this.selector = selector;
}
@Override
public void run() {
try {
//获取通道
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel s = ssc.accept();
s.configureBlocking(false);
//将需要重新注册
NEED_REG_DISPATH.set(true);
//注册读
READ_QUEUE.add(new ChannelAndAtt(s,new ByteArrayOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
//打断mian线程阻塞
selector.wakeup();
}
}
public static class ChannelAndAtt{
private SelectableChannel channel;
private Object att;
public ChannelAndAtt(SelectableChannel channel, Object att) {
this.channel = channel;
this.att = att;
}
public SelectableChannel getChannel() {
return channel;
}
public void setChannel(SelectableChannel channel) {
this.channel = channel;
}
public Object getAtt() {
return att;
}
public void setAtt(Object att) {
this.att = att;
}
}
}
- 一个小小的分析:
宏观上来看,首先两个线程池master主负责转发和worker子负责读写;另外三个变量,AtomicBoolean表示当前是否需要注册转发,和另外两个读 与 写队列。
在main函数中,所有的处理都先是key.cancel( ),为了防止一个事件被多线程同时处理。在子线程中分别操纵那三个变量。
要重复注册请求转发的时候,还需要读用户请求,所以操作两个变量;select()中增加timeout参数可以自动打破阻塞,可以及时的进行注册。
在各个变量对应的方法中,先拿通道,再那对应的事件,进行核心业务读缓冲区,或者写数据之类的操作。写完之后shutdownOutput(),关流操作。
四、RPC 设计雏形