Selector和非阻塞网络编程
要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。
选择器提供选择执行已经就绪的任务的能力.从底层来看,Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。Selector 允许单线程处理多个Channel。仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道,这样会大量的减少线程之间上下文切换的开销。
**选择器(Selector):**Selector选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
**可选择通道(SelectableChannel):**SelectableChannel这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。因为FileChannel类没有继承SelectableChannel因此是不是可选通道,而所有socket通道都是可选择的,SocketChannel和ServerSocketChannel是SelectableChannel的子类。
**选择键(SelectionKey):**选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register()返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数的形式进行编码),选择键支持四种操作类型:
- Connect 连接
- Accept 接受请求
- Read 读
- Write 写
NIO实现服务器(单线程接受多个客户端) (如果理解不了,看最下边自己画的图)
适合短暂客户,节省了创建销毁线程的空间和性能
流程图:
//服务器:
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;
//单线程接受多个客户端请求
public class Demo1 {
public static void main(String[]args) throws Exception{
ServerSocketChannel ssc=ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(65524));
System.out.println("服务器已开启");
//设置为非阻塞 非阻塞解释在9.1
ssc.configureBlocking(false);
//创建轮询器selector
Selector selector= Selector.open();
//注册轮询器,更新通道状态为就绪
ssc.register(selector, SelectionKey.OP_ACCEPT);//注册一个服务器通道,用来接收客户端的连接请求
while(selector.select()>0){ //首先调用select()方法,该方法会阻塞,直到至少有一个事件发生,然后再使用selectedKeys()方法获取发生事件的selectionKey,再使用迭代器进行循环。
Set<SelectionKey> selectionKeys = selector.selectedKeys(); //取出所有事件的selectionkey放入集合,用迭代器循环
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey=iterator.next();
if(selectionKey.isAcceptable()){ //如果满足Acceptable条件,则必定是一个ServerSocketChannel通道中的事件
// ServerSocketChannel sscTemp = (ServerSocketChannel) selectionKey.channel(); //可以不写,如果服务器注册了多条ServerSocketChannel,需要写,用来找出对应的服务器通道。本题只注册了一个服务器通道ServerSocketChannel来接收客户端连接信号,所以不需要写
SocketChannel socketChannel=sscTemp.accept(); //ServerSocketChannel服务器通道中为OP_ACCEPT的操作,所以接受为一个对应的Socketchannel(客户端通道)
socketChannel.configureBlocking(false); //设置为非阻塞
socketChannel.register(selector,SelectionKey.OP_READ); //把此该socketChannel注册到Selector上,成为一个客户端通道,键为OP_read,说明此客户端已经与服务器建立连接,该通道对应的客户端以后发来的信息,会进行读取操作。
}else if(selectionKey.isReadable()){ //如果满足Readable条件,则必定是一个之前已经建立过连接的SocketChannel
//接收数据
SocketChannel sc=(SocketChannel) selectionKey.channel();
//找出此selectionKey对应的SocketChannel,读取此socketChannel(客户端)中的信息
ByteBuffer buf=ByteBuffer.allocate(1024*4);
int len;
while((len=sc.read(buf))>0){ //此时的read不会阻塞
buf.flip();
String str=new String(buf.array(),0,buf.limit());
System.out.println(sc.getRemoteAddress()+":"+str);
buf.clear();
}
if(len==-1){
sc.close();
}
}
iterator.remove();
}
}
}
}
//客户端(客户端一般不需要设置不阻塞,此代码仅仅为演示不阻塞)
package NIO;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class Demo1Client {
public static void main(String[] args) throws Exception{
InetSocketAddress is=new InetSocketAddress("10.9.21.178",65524);
SocketChannel sc=SocketChannel.open(is);
sc.configureBlocking(false);
Scanner input=new Scanner(System.in);
ByteBuffer bf=ByteBuffer.allocate(1024*4);
while(true){
String str=input.next();
bf.put(str.getBytes());
bf.flip();
sc.write(bf);
bf.clear();
if(str.equals("886")){
break;
}
}
sc.close();
}
}