网络编程模型 nio

末鹿安然 提交于 2020-02-04 23:15:32

1.简介

nio 是new io的简称,1.4之后提供。特性:为所有的原始类型提供缓存支持(Buffer),字符集编码解码解决方案,channel一个原始的i/o抽象,支持锁和内存映射文件的文件访问接口,提供多路(non-blcoking)非阻塞式的高伸缩性网络。

2.比较

bio 同步阻塞,jdk1.4之前使用,阻塞到读写方法,阻塞到线程来提高并发性能,效果一般

nio 同步非阻塞io,jdk1.4后,linux多路复用技术,实现io的轮询方式,目前是主流的网络通信模式,netty 框架,

aio 异步非阻塞io ,jdk1.7后,基于linux的epoll模式,目前使用较少,

3.基本编程模型

服务端:

核心api ServerSocket

流程 ; 先创建一个服务,然后绑定在服务器的ip地址和端口

等待客户端的链接请求

收到连接请求后,接受请求,建立了一个tcp连接

从建立的连接中获取到socket输入、输出流(同步阻塞)

通过两个流进行数据的交互

客户端:

核心api Socket

流程 : 先向服务端请求连接

一旦被服务器接受,连接就创建好了

从tcp连接中获取socket输入、输出流

通过两个流进行数据的交互

3.原理

阻塞和非阻塞:阻塞和非阻塞是进程在访问数据时,数据是否准备就绪的一种处理方式,当数据没有准备好的时候,

阻塞是往往需要等待缓冲区中的数据准备好之后才处理,否则一直等待,非阻塞是当我们的进程访问我们

的数据缓冲区的时候,数据没有准备好的时候,直接返回,不需要等待,有数据的时候,也直接返回。

同步和异步:同步和异步都是基于应用程序和操作系统处理io事件所采用的方式。同步就是应用程序要直接参与io事件的操作,

异步是所有的io读写事件都交给操作系统去处理。同步的方式在处理io时间的时候,必须阻塞在某个方法上面去等待我们

的io事件完成(阻塞在io事件或者通过轮询io事件的方式),对于异步来说,所有的io读写都交给了操作系统,这个时候我们就

可以去做其他的事情,并不需要去完成真正的io操作,当操作系统完成io之后,给我们的应用程序一个通知就可以了。

同步有两种实现模式:

第一种是,阻塞到io事件,阻塞到read或者write方法上,这个时候我们只能把读写方法放置到线程中,然后阻塞线程的方式来实现并发服务,

对线程的性能开销比较大。

第二种是,io事件的轮询,在linux c语言编程中叫做多路复用技术(select模式),读写事件交给一个专门的线程来处理,这个线程完成

完成io事件的注册功能,还有就是不断地去轮询我们的读写缓冲区(操作系统),看是否有数据准备好,然后通知我们的相应的业务处理线程,

这样的话,我们的业务处理线程就可以做其他的事情,在这种模式下,阻塞的不是所有的io线程,而是阻塞的只是select线程。

nio原理: 通过selector(选择器),管理所有的io事件。客户端的connection事件、服务端的accept事件、服务端和客户端的读写事件。

当io事件注册给选择器时,选择器会分配他们一个key,可以简单的理解成一个事件的标签,当io事件就绪后,可以通过key值来找到相应的

管道channel,然后通过管道发送数据和接收数据等操作。数据缓冲区bytebuffer

当从客户端读取数据时,会先执行bytebuff.clear(),所以position指向0,limit直接结尾,mark也指向0,读取数据where到bytebuff时,mark指向了4,执行flip之后,limit指向mark指向的位置,position到limit中间的数据就是这次读取进来的数据。所以flip方法所做的操作就是把positon指向本次数据的起始点,把limit指向本次数据的结束点。Clear方法是把position指针指向缓存区的起始点,把limit指针指向本次数据的结束点。

5.nioserver和niocilent的代码示例

public class NioServer {
    public ByteBuffer readBuffer;//应用程序和操作系统之间交互数据存放区
    public Selector selector;//轮循器
    
    public static void main(String[] args) {
        NioServer server = new NioServer();
        server.init();
        System.out.println("server started --> 8383");
        server.listen();
    }
    
    private void init() {
        readBuffer=ByteBuffer.allocate(1024); //分配一个1024字节的缓存
        ServerSocketChannel servSocketChannel;//服务器端和客户端数据交互的通道
        try {
            servSocketChannel = ServerSocketChannel.open();//获取通道
            servSocketChannel.configureBlocking(false);//设置通道为非阻塞的方式
            servSocketChannel.socket().bind(new InetSocketAddress(8383));//将通道绑定在某个服务器 的ip地址和某个端口上
            selector = Selector.open();//打开一个多路复用器(选择器)
            // 将上面创建好的socket channel注册到selector多路复用器上,
            // 对于服务端来说,一定要先注册一个OP_ACCEPT事件用来响应客户端的连接请求
            servSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    private void listen() {
        while (true) {
            try {
                selector.select();// 去询问一次selector选择器
                Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
                while (ite.hasNext()) {
                    // 遍历到一个事件key
                    SelectionKey key = ite.next();
                    ite.remove(); // 确保不重复处理
                    // 处理事件
                    handleKey(key);
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    }
    
    
    private void handleKey(SelectionKey key) throws Exception {
        SocketChannel channel = null;
            try {
                if (key.isAcceptable()) {//第一次事件肯定是接受连接的事件
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    channel=serverChannel.accept();//接受连接请求
                    channel.configureBlocking(false);//设置为非阻塞的方式
                    channel.register(selector, SelectionKey.OP_READ);//当接受请求之后,注册感兴趣的事件为读事件
                }else if(key.isReadable()){
                    channel = (SocketChannel) key.channel();
                    // 先清空一下buffer
                    readBuffer.clear();
                    int count = channel.read(readBuffer);//将channel通道中的数据读取到readBuffer
                    if (count > 0) {
                        readBuffer.flip();
                        CharBuffer charBuffer = CharsetHelper.decode(readBuffer);//将二进制readBuffer转换成字符charBuffer
                        String question = charBuffer.toString();//得到字符串
                        // 根据客户端的请求,调用相应的业务方法获取业务结果
                        String answer = getAnswer(question);
                        channel.write(CharsetHelper.encode(CharBuffer.wrap(answer)));
                    }
                }else {
                    // 这里关闭channel,因为客户端已经关闭channel或者异常了
                    channel.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                if (channel != null) {
                    channel.close();
                }
            }
        
    }
    
    private String  getAnswer(String question) {
        String answer = null;

        switch (question) {
        case "who":
            answer = "我是凤姐\n";
            break;
        case "what":
            answer = "我是来帮你解闷的\n";
            break;
        case "where":
            answer = "我来自外太空\n";
            break;
        case "hi":
            answer = "hello\n";
            break;
        case "bye":
            answer = "88\n";
            break;
        default:
            answer = "请输入 who, 或者what, 或者where";
        }
        return answer;
    }
}
 

 

 

public class NioClient implements Runnable{
        private BlockingQueue<String> words;
        private Random random;
         
        public static void main(String[] args) {      
            // 多个线程发起Socket客户端连接请求
            for(int i=0; i<10; i++){
                NioClient c = new NioClient();
                c.init();
                new Thread(c).start();
            }      
        }
     
        @Override
        public void run() {     
            SocketChannel channel = null;
            Selector selector = null;
            try {
                channel = SocketChannel.open();
                channel.configureBlocking(false);
                // 主动请求连接
                channel.connect(new InetSocketAddress("localhost", 8383));
                selector = Selector.open();
                channel.register(selector, SelectionKey.OP_CONNECT);
                boolean isOver = false;
                 
                while(! isOver){
                    selector.select();
                    Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
                    while(ite.hasNext()){
                        SelectionKey key = ite.next();
                        ite.remove();
                         
                        if(key.isConnectable()){
                            if(channel.isConnectionPending()){
                                if(channel.finishConnect()){
                                     
                                    //只有当连接成功后才能注册OP_READ事件
                                    key.interestOps(SelectionKey.OP_READ);
                                    channel.write(CharsetHelper.encode(CharBuffer.wrap(getWord())));
                                    sleep();
                                }
                                else{
                                    key.cancel();
                                }
                            }                                              
                        }
                        else if(key.isReadable()){
                            ByteBuffer byteBuffer = ByteBuffer.allocate(128);                       
                            channel.read(byteBuffer);
                            byteBuffer.flip();
                            CharBuffer charBuffer = CharsetHelper.decode(byteBuffer);
                            String answer = charBuffer.toString(); 
                            System.out.println(Thread.currentThread().getId() + "---" + answer);
                             
                            String word = getWord();
                            if(word != null){
                                channel.write(CharsetHelper.encode(CharBuffer.wrap(word)));
                            }
                            else{
                                isOver = true;
                            }
                            sleep();                       
                        }
                    }
                }                          
            } catch (IOException e) {
               // TODO
            }
            finally{
                if(channel != null){
                    try {
                        channel.close();
                    } catch (IOException e) {                      
                        e.printStackTrace();
                    }                  
                }
                 
                if(selector != null){
                    try {
                        selector.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
     
        private void init() {
            words = new ArrayBlockingQueue<String>(5);
            try {
                words.put("hi");
                words.put("who");
                words.put("what");
                words.put("where");
                words.put("bye");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }  
             
            random = new Random();
        }
         
        private String getWord(){
            return words.poll();
        }
     
        private void sleep() {
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(3));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }  
         
        private void sleep(long l) {
            try {
                TimeUnit.SECONDS.sleep(l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
}

 

public class CharsetHelper {
    private static final String UTF_8 = "UTF-8";
    private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
    private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
     
    public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
        return encoder.encode(in);
    }
 
    public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
        return decoder.decode(in);
    }
}

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!