zoukankan      html  css  js  c++  java
  • 第2章 NIO入门

    2.1 传统的BIO编程

      以服务器为例,在传统BIO模型下的服务器,每当一个新的请求到来的时候回分配一个线程去处理该请求,并且该线程在执行IO操作的时候会一直阻塞,知道IO操作完成或抛出异常才会返回。当网络情况不佳时,网络IO可能会耗费大量时间,那么就会同时有大量线程在服务器上阻塞着,很容易造成内存溢出。

      这种模型被称为同步阻塞模型,同步指的是只有等待线程IO操作完成该线程才会返回,阻塞指的是IO没有完成的时候一直等待。

      

    2.1.1 服务端代码

      Server类,监听8080端口,在while循环里Server端阻塞在server.accept上,即等待请求传到8080端口上,从accept方法返回。后续new Thread是新建一个线程去处理请求。

    public class TimeServer {
    public static void main(String[] args) throws IOException {
    int port = 8080;
    ServerSocket server = null;

    try {
    server = new ServerSocket(port);
    System.out.println("The server start in port "+port);
    Socket socket = null;
    while (true){
    socket = server.accept();
    Thread thread = new Thread(new TimeServerHandler(socket));
    thread.start();
    }


    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    if (server!=null){
    System.out.println("Time server close");
    server.close();
    server=null;
    }
    }
    }
    }

      执行代码用jstack打印线程状态,看到server线程在17行“停止”,即阻塞在17行等待请求传过来。这种阻塞就是BIO里的B,block,一个IO没有完成就一直卡在那里。

      有新的客户端接入新建线程执行处理方法,通过检查传过来的字符串是否是要求的“QUERY TIME ORDER”,如果是就返回当前服务器的时间,否则返回错误信息。

    public class TimeServerHandler implements Runnable {
        private Socket socket;
    
        public TimeServerHandler(Socket socket) {
            this.socket = socket;
        }
    
        public void run() {
            BufferedReader in = null;
            PrintWriter out = null;
    
            try {
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream(),true);
                String currentTime = null;
                String body = null;
                while (true){
                    body = in.readLine();
                    if (body == null){
                        break;
                    }
                    System.out.println("The time server receive order: "+body);
                    currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)?new Date(System.currentTimeMillis()).toString():"BAD ORDER";
                    out.println(currentTime);
                }
            } catch (IOException e) {
                e.printStackTrace();
                if (in!=null){
                    try {
                        in.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
                if (out!=null){
                    out.close();
                    out = null;
                }
    
                if (socket!=null){
                    try {
                        socket.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }finally {
                        socket=null;
                    }
                }
            }
        }
    }

       同时开启服务端和客户端后可以在控制台看到输出。

    2.1.2 缺点

    • 每个请求都需要一个新建线程去处理,扛不住太大的并发,因为没有给线程数目设置瓶颈。
    • BIO会在网络不佳情况导致大量线程。

    2.2 伪异步IO编程

    2.2.1 代码

      用线程池代替不停的新建线程,好处是线程池是有界的,避免在极端情况下不停新建线程。

    public class TimeServer_ThreadPool {
        public static void main(String[] args) {
            int port = 8080;
            ServerSocket server = null;
            try {
                server = new ServerSocket(port);
                System.out.println("server start at port "+ port);
                Socket socket = null;
                ExecutorService pool = Executors.newFixedThreadPool(10);
                while (true){
                    socket = server.accept();
                    pool.execute(new TimeServerHandler(socket));
                }
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    2.2.2 弊端

    • 使用BIO读取数据时,线程会一直阻塞直到 1、有数据读 2、数据读取完毕 3、抛出异常。当客户端请求发送较慢或者网络时延较时,读取数据的线程会一直阻塞。
    • 使用BIO输出数据时,线程会一直阻塞直到  1、有数据写 2、数据写完 3、抛出异常。当服务端写数据时,如果网络情况不佳,客户端不能及时读取数据,大量数据留在TCP缓冲区,当发送端即服务端的Window size为0的时候写线程就会无法继续写从而阻塞。
    • 无论读还是写,都是阻塞的,阻塞与否以及阻塞是否严重依赖于网络传输的质量。
    • 当线程池的队列使用阻塞队列时,前台线程负责把请求封装成对象加入线程池的阻塞队列,如果网络状况十分的差,阻塞队列也满了,那么复制把请求对象加入阻塞队列的前台线程也会阻塞,整个系统失去异步性,所有的请求都会超时。

      

    2.3 NIO 编程

      NIO与BIO的区别在两点

    • 面向的对象不同。BIO面向Stream,该Stream是单向通信,只能是读Stream或者写Stream。NIO面向Buffer,NIO面向buffer和channel,channel是铁路,buffer是铁路上运输的数据。
    • 阻塞性。BIO是阻塞的,NIO通过Selector实现多路复用。

    2.3.1 Buffer与Channel

      

  • 相关阅读:
    随想13:论“善”字
    Nginx做前端Proxy时TIME_WAIT过多的问题
    HTTP的长连接和短连接
    nginx长连接的问题
    Tomcat性能参数设置
    Nginx1.1.4+ 对后端机器的长连接特性
    HTTP长连接200万尝试及调优方法
    NGINX轻松管理10万长连接 --- 基于2GB内存的CentOS 6.5 x86-64
    CRtmpServer转推流到Nginx Rtmp及SRS(SimpleRtmpServer)的经历
    rtmp流媒体编程相关整理2013(crtmpserver,rtmpdump,x264,faac)
  • 原文地址:https://www.cnblogs.com/AshOfTime/p/10806165.html
Copyright © 2011-2022 走看看