Socket的用法——NIO包下SocketChannel的用法

Socket属于IO的一种,nio提供了ServerSocketChannel和SocketChannel。
普通Socket和NioSocket的区别:
    普通Socket是客户端发出一次请求、服务端接收到后响应、客户端接收到服务端的响应才能再次请求。
    NioSocket是引入了三个概念:Channel、Selector、Buffer。Buffer是将很多请求打包,一次性发出去,有Selector扮演选择器的角色,将请求分转给对应的通道(Channel)进行处理响应。
 
NIO之Buffer缓冲器
    Buffer是java.nio包的一个类,主要用来存放数据。
    4个重要的属性:
        1、capacity——容量。表示Buffer最多可以保存元素的数量,在创建Buffer对象是设置,设置后不可改变。
        2、limit——可以使用的最大数量。limit在创建对象时默认和capacity相等,若是有专门设置,其值不可超过capacity,表示可操作元素个数。capacity=100,表示最多可以保存100个,假设现在只存了20个就要读取,这是limit会被设为20.
        3、position——当前所操作元素的位置索引,从0开始,随着get方法和put方法进行更新。
        4、mark——用来暂存position。起中间变量的作用,使用mark保存当前position的值后,进行相关操作position会产生变化,若使用reset()方法,便会根据mark的值去恢复position操作前的索引。mark默认-1,取值不可超过position。
        大小关系:mark<=position<=limit<=capacity
    初始化方法:clear(),用于写数据前初始化相关属性值。
        clear()初始化的是mark、position和limit的值,将mark=-1,position=0,limit=capacity。
    位置置0方法:rewind(),读写模式都可以用。
        将matk=-1,position = 0。limit和调用该方法前相同。
    读模式转换方法:flip(),用于将写模式转换为读模式。写模式下position是随着数据的写入变化的,最后position会位于写入的最后一个元素的位置。flip方法转换读模式是将这个position的值用来设置limit,这样limit就成为了现在buffer中实际存放的元素个数,然后再将position置0,表示从头开始读,mark初始化为-1。

ServerSocketChannel
    创建:通过ServerSocketChannel类的静态方法open()获得。
    绑定端口:每个ServerSocketChannel都有一个对应的ServerSocket,通过其socket()方法获得。获得ServerSocket是为了使用其bind()方法绑定监听端口号。若是使用其accept()方法监听请求就和普通Socket的处理模式无异。
    设置是否使用阻塞模式:true/false。configureBlocking(false)——不适用阻塞模式。阻塞模式不能使用Selector!
    注册选择器以及选择器关心的操作类型:register(Selector,int)     第一个参数可以传入一个选择器对象,第二个可传入SelectionKey代表操作类型的四个静态整型常量中的一个,表示该选择器关心的操作类型。
Selector
    创建:通过Selector的静态方法open()获得。
    等待请求:select(long)——long代表最长等待时间,超过该时间程序继续向下执行。若设为0或者不传参数,表示一直阻塞,直到有请求。
    获得选择结果集合:selectedKeys(),返回一个SelectionKey集合。SelectionKey对象保存处理当前请求的Channel、Selector、操作类型以及附加对象等等。
    SelectionKey对象有四个静态常量代表四种操作类型以及对应的判断是否是该种操作的方法:
                        SelectionKey.OP_ACCEPT——代表接收请求操作                isAcceptable()    
                        SelectionKey.OP_CONNECT——代表连接操作                   isConnectable()
                        SelectionKey.OP_READ——代表读操作                              isReadable()
                        SelectionKey.OP_WRITE——代表写操作                            isWritable()

NioSocket中服务端的处理过程分为5步:
    1、创建ServerScoketChannel对象并设置相关参数(绑定监听端口号,是否使用阻塞模式)
    2、创建Selector并注册到服务端套接字信道(ServerScoketChannel)上
    3、使用Selector的select方法等待请求
    4、接收到请求后使用selectedKeys方法获得selectionKey集合
    5、根据选择键获得Channel、Selector和操作类型进行具体处理。

服务端代码实现如下,其中包括一个静态内部类Handler来作为处理器,处理不同的操作。 注意在遍历选择键集合时,没处理完一个操作,要将该请求在集合中移除。
/*
模拟服务端 -nio-Socket 实现
*/
public class  NIOServer  {
    public static void  main ( String []  args ) {
        try  {
            // 创建 ServerSocketChannel 通道,绑定监听端口为 8080
            ServerSocketChannel  serverSocketChannel =  ServerSocketChannel . open () ;
            serverSocketChannel. socket ().bind( new  InetSocketAddress( 8080 )) ;
            // 设置为非阻塞模式
            serverSocketChannel.configureBlocking( false ) ;
            // 注册选择器 , 设置选择器选择的操作类型
            Selector  selector =  Selector . open () ;
            serverSocketChannel.register(selector ,  SelectionKey . OP_ACCEPT ) ;
            // 创建处理器
            Handler  handler =  new  Handler( 1204 ) ;
            while  ( true ) {
                // 等待请求,每次等待阻塞 3s ,超过时间则向下执行,若传入 0 或不传值,则在接收到请求前一直阻塞
                if  (selector. select ( 3000 ) ==  0 ) {
                    System . out .println( " 等待请求超时 ......" ) ;
                    continue ;
                }
                System . out .println( "----- 处理请求 -----" ) ;
                // 获取待处理的选择键集合
                Iterator< SelectionKey > keyIterator = selector. selectedKeys (). iterator () ;
                while  (keyIterator. hasNext ()) {
                    SelectionKey  selectionKey = keyIterator. next () ;
                    try  {
                        // 如果是连接请求,调用处理器的连接处理方法
                        if (selectionKey.isAcceptable()){
                            handler.handleAccept(selectionKey) ;
                        }
                        // 如果是读请求,调用对应的读方法
                        if  (selectionKey.isReadable()) {
                            handler.handleRead(selectionKey) ;
                        }
                    }  catch  ( IOException  e ) {
                        keyIterator. remove () ;
                        continue ;
                    }
                }
                // 处理完毕从待处理集合移除该选择键
                keyIterator. remove () ;
            }
        }  catch  ( IOException  e ) {
            e .printStackTrace() ;
        }
}

    /*
        处理器类
    */
    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  selectionKey )  throws  IOException  {
            // 通过选择器键获取服务器套接字通道,通过 accept() 方法获取套接字通道连接
            SocketChannel  socketChannel = (( ServerSocketChannel ) selectionKey . channel ()). accept () ;
            // 设置套接字通道为非阻塞模式
            socketChannel.configureBlocking( false ) ;
            // 为套接字通道注册选择器,该选择器为服务器套接字通道的选择器,即选择到该 SocketChannel 的选择器
            // 设置选择器关心请求为读操作,设置数据读取的缓冲器容量为处理器初始化时候的缓冲器容量
            socketChannel.register( selectionKey . selector () , SelectionKey . OP_READ ,  ByteBuffer . allocate ( bufferSize )) ;
        }

        public void  handleRead ( SelectionKey  selectionKey )  throws  IOException  {
            // 获取套接字通道
            SocketChannel  socketChannel = ( SocketChannel )  selectionKey . channel () ;
            // 获取缓冲器并进行重置 ,selectionKey.attachment() 为获取选择器键的附加对象
            ByteBuffer  byteBuffer = ( ByteBuffer ) selectionKey .attachment() ;
            byteBuffer.clear() ;
            // 没有内容则关闭通道
            if  (socketChannel. read (byteBuffer) == - 1 ) {
                socketChannel.close() ;
            }  else  {
                // 将缓冲器转换为读状态
                byteBuffer.flip() ;
                // 将缓冲器中接收到的值按 localCharset 格式编码保存
                String  receivedRequestData =  Charset . forName ( localCharset ). newDecoder ().decode(byteBuffer).toString() ;
                System . out .println( " 接收到客户端的请求数据: " +receivedRequestData) ;
                // 返回响应数据给客户端
                String  responseData =  " 已接收到你的请求数据,响应数据为: ( 响应数据 )" ;
                byteBuffer =  ByteBuffer . wrap (responseData.getBytes( localCharset )) ;
                socketChannel. write (byteBuffer) ;
                // 关闭通道
                socketChannel.close() ;
            }
        }
    }
}
    

你可能感兴趣的:(java)