NioSocket的用法

不知道该咋说(ง •_•)ง

ServerSocketChannel和SocketChannel,它们对应原来的ServerSocket和Socket。

Buffer、Channel和Selector

Buffer就是所要送的货物,Channel就是送货员(或者开往某个区域的配货车),Selector就是中转站的分拣员。

NioSocket使用中首先要创建ServerSocketChannel,然后注册Selector,接下来就可以用Selecto接收请求并处理了。

ServerSocketChannel可以使用自己的静态工厂方法open创建。每个ServerSocketChannel对应一个ServerSocket,可以调用其socket方法来获取,一般使用获取到的ServerSocket来绑定端口。ServerSocketChannel可以通过configureBlocking方法设置是否采用阻塞模式,如果要采用非阻塞模式可以用configureBlocking(false)来设置,设置了非阻塞模式之后就可以调用register方法注册Selector来使用了(阻塞模式不可以使用Selector)。

Selector可以通过其静态工厂方法open创建,创建后通过Channel的register方法注册到ServerSocketChannel或者SocketChannel上,注册完之后Selector就可以通过select方法来等待请求,select方法有一个long类型的参数,代表最长等待时间,如果在这段时间里接收到了相应操作的请求则返回可以处理的请求的数量,否则在超时后返回0,程序继续往下走,如果传入的参数为0或者调用无参数的重载方法,select方法会采用阻塞模式直到有相应操作的请求出现。当接收到请求后Selector调用selectedKeys方法返回SelectionKey的集合。

 SelectionKey保存了处理当前请求的Channel和Selector,并且提供了不同的操作类型。Channel在注册Selector的时候可以通过register的第二个参数选择特定的操作,这里的操作就是在SelectionKey中定义的,一共有4种:
·SelectionKey.OP_ACCEPT  接受请求操作
·SelectionKey.OP_CONNECT  连接操作
·SelectionKey.OP_READ  读操作
·SelectionKey.OP_WRITE  写操作
只有在register方法中注册了相应的操作Selector才会关心相应类型操作的请求。Channel和Selector并没有谁属于谁的关系,就好像一个分拣员可以为多个地区分拣货物而每个地区也可以有多个分拣员来分拣一样,它们就好像数据库里的多对多的关系,不过Selector这个分拣员分拣得更细,它可以按不同的类型来分拣,分拣后的结果保存在SelectionKey中,可以分别通过SelectionKey的channel方法和selector方法来获取对应的Channel和Selector,而且还可以通过isAcceptable、isConnectable、isReadable和isWritable方法来判断是什么类型的操作。
 
NioSocket中服务端的处理过程可以分为5步:
1)创建ServerSocketChannel并设置相应参数。
2)创建Selector并注册到ServerSocketChannel上。
3)调用Selector的select方法等待请求。
4)Selector接收到请求后使用selectedKeys返回SelectionKey集合。
5)使用SelectionKey获取到Channel、Selector和操作类型并进行具体操作。
 
public class NIOServer {
    public static void main(String[] args) throws Exception {
        //创建ServerSocketChannel,监听8080端口
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(8080));//使用获取到的ServerSocket来绑定端口
        //设置为非阻塞模式,阻塞模式不可以使用Selector
        ssc.configureBlocking(false);
        //为ssc注册选择器
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        //创建处理器
        Handler handler = new Handler(1024);
        while(true){
            //等待请求,每次等待阻塞3秒,超过3s后线程继续向下运行,如果传入0或者不传参数将一直阻塞
            if(selector.select(3000)==0){
                System.out.println("等待请求超时......");
                continue;
            }
            System.out.println("处理请求......");
            //获取待处理的SelectionKey
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while(keyIterator.hasNext()){
                SelectionKey key = keyIterator.next();
                try{//接收到连接请求时
                    //如果是接受请求操作
                    if(key.isAcceptable()){
                        handler.handleAccept(key);
                    }
                    //如果是读数据操作
                    if(key.isReadable()){
                        handler.handleRead(key);
                    }
                    //还有isConnectable,isWritable
                }catch (IOException ex){
                    //处理完后,从待处理的SelectionKey迭代器中移除当前所使用的key
                    keyIterator.remove();
                    continue;
                }
                keyIterator.remove();
            }
        }
    }

    private static class Handler{
        private int bufferSize = 1024;
        private String localCharset = "UTF-8";
        public Handler(){}
        public Handler(int bufferSize){
            this(bufferSize,null);
        }
        public Handler(String localCharset){
            this(-1,localCharset);
        }
        public Handler(int bufferSize, String localCharset) {
            if (bufferSize > 0)
                this.bufferSize = bufferSize;
            if (localCharset != null)
                this.localCharset = localCharset;
        }
public void handleAccept(SelectionKey key) throws IOException{ SocketChannel SC = ((ServerSocketChannel)key.channel()).accept(); SC.configureBlocking(false); SC.register(key.selector(),SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize)); } public void handleRead(SelectionKey key) throws IOException{ //获取channel SocketChannel sc = (SocketChannel)key.channel(); //获取buffer并重置 ByteBuffer buffer = (ByteBuffer)key.attachment(); buffer.clear(); //没有读到内容则关闭 if(sc.read(buffer) == -1){ sc.close(); }else{ //将buffer转换为读状态 buffer.flip(); //将buffer中接收到的值按localCharset格式编码后保存到receivedString String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString(); System.out.println("received from cient: "+receivedString); //返回数据给客户端 String sendString = "received data: "+receivedString; buffer = ByteBuffer.wrap(sendString.getBytes(localCharset)); sc.write(buffer); //关闭Socket sc.close(); } } } }

 

 
上面的处理过程都做了注释,main方法启动监听,当监听到请求时根据SelectionKey的状态交给内部类Handler进行处理,Handler可以通过重载的构造方法设置编码格式和每次读取数据的最大值。Handler处理过程中用到了Buffer,Buffer是java.nio包中的一个类,专门用于存储数据,Buffer里有4个属性非常重要,它们分别是:
·capacity:容量,也就是Buffer最多可以保存元素的数量,在创建时设置,使用过程中不可以改变;
 
·limit:可以使用的上限,开始创建时limit和capacity的值相同,如果给limit设置一个值之后,limit就成了最大可以访问的值,其值不可以超过capacity。比如,一个Buffer的容量capacity为100,表示最多可以保存100个数据,但是现在只往里面写了20个数据然后要读取,在读取的时候limit就会设置为20;
 
·position:当前所操作元素所在的索引位置,position从0开始,随着get和put方法自动更新;
 
·mark:用来暂时保存position的值,position保存到mark后就可以修改并进行相关的操作,操作完后可以通过reset方法将mark的值恢复到position。
  比如,Buffer中一共保存了20个数据,position的位置是10,现在想读取15到20之间的数据,这时就可以调用Buffer#mark()将当前的position保存到mark中,然后调用Buffer#position(15)将position指向第15个元素,这时就可以读取了,读取完之后调用Buffer#reset()就可以将position恢复到10。mark默认值为-1,而且其值必须小于position的值,如果调用Buffer#position(int newPosition)时传入的newPosition比mark小则会将mark设为-1。
 
这4个属性的大小关系是:mark<=position<=limit<=capacity。理解了这4个属性,Buffer就容易理解了。
 
我们这里的NioServer用到clear和flip方法,clear的作用是重新初始化limit、position和mark三个属性,让limit=capacity、position=0、mark=-1。
flip方法的作用是这样的:在保存数据时保存一个数据position加1,保存完了之后如果想读出来就需要将最好position的位置设置给limit,然后将position设置为0,这样就可以读取所保存的数据了,flip方法就是做这个用的,这两个方法的代码如下:
 
 // java.nio.Buffer 

public final Buffer clear() {
     position = 0;
     limit = capacity;
     mark = -1;
     return this;
 }

 public final Buffer flip() {
     limit = position;
     position = 0;
     mark = -1;
     return this;
 } 

 

posted @ 2018-03-31 14:52  -桃之夭夭  阅读(228)  评论(0编辑  收藏  举报