netty是如何一步一步演化而来的,不断进化成就netty

1,718 阅读6分钟

这是我参与更文挑战的第14天,活动详情查看: 更文挑战

简介

  • 随着IO类库不断完善,我们基于Java的网络编程学习成本越来越低。之前网络开发都是底层语言编写的,像C语言,C++。其实通过Java也可以编写。Java的io类基于tcp连接提供的socket就是我们实现网络开发的桥段。但是因为是同步阻塞式,所以效率上来说就慢了很多。在Java7之后提供了异步阻塞式io。为我们拉开了一个新天地。

BIO

  • 什么是网络编程?所谓的网络编程其实就是C/S模型。大家都知道Java是开发B/S模型的。C/S实际是client和server端的开发。说白就是两个进程相互通信。client通过tcp连接server然后交互数据
  • BIO 即同步阻塞是编程。在JDK1.4之前我们网络连接都是采用的BIO模式,服务端通过ServerSocket(port)构建服务端。然后服务端通过accept方法阻塞式等待客户端的连接。客户端通过Socket(ip,port)构建客户端。然后通过PrintWriter传递消息。
  • 默认情况下BIO模式是一个客户端对应一个线程。这样对于内存的消耗是严重的。慢慢的这种方式也就被抛弃了。

001.jpg

server

  • 为了缓解线程压力。这里构造线程池。初始大小5个

private static final ThreadLocal<ExecutorService> executorService = new ThreadLocal<ExecutorService>() {
    @Override
    protected ExecutorService initialValue() {
        return Executors.newFixedThreadPool(5);
    }
};

  • 然后是构建ServerSocket。然后就是一直在阻塞式等待客户端的连接。什么叫阻塞式等待。就是一直在等待客户端连接。没有新的客户端连接就不继续执行。当客户端连接后。accept就会返回当前客户端的连接对象。然后我们将他进行封装后放到线程池中。线程池中有可用资源就会将可用线程加载这个客户端对象。

public static void start() throws IOException {
    try {
        // 通过构造函数创建ServerSocket
        server = new ServerSocket(HostConstant.PORT);
        System.out.println("服务器已启动,端口号:" + HostConstant.PORT);
        while (true) {
            // 真正处理的还是Socket
            Socket socket = server.accept();// 阻塞方法
            // 把客户端请求打包成一个任务,放到线程池执行
            executorService.get().execute(new ServerHandler(socket));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (server != null) {
            server.close();
        }
    }

}


public class ServerHandler implements Runnable {
    private Socket socket;

    public ServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
            PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
            String message;
            String result;
            // 通过输入流读取客户端传输的数据
            while ((message = br.readLine()) != null) {
                System.out.println("server receive data:" + message);
                result = response(message);
                // 将业务结果通过输出流返回给客户端
                pw.println(result);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }

    // 返回给客户端的应答
    public static String response(String msg) {
        return "Hello," + msg + ",Now is " + new java.util.Date(System.currentTimeMillis()).toString();
    }
}

##client


public class BIOClient {
    public void startConnect() {
        try {
            Socket socket = new Socket(HostConstant.IP, HostConstant.PORT);
            new ReadMsg(socket).start();
            PrintWriter pw = null;
            // 写数据到服务端
            pw = new PrintWriter(socket.getOutputStream());
            pw.println(UUID.randomUUID());
            pw.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static class ReadMsg extends Thread {
        Socket socket;

        public ReadMsg(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
                String line = null;
                // 通过输入流读取服务端传输的数据
                while ((line = br.readLine()) != null) {
                    System.out.printf("%s\n", line);
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Integer time = 6;
        for (Integer i = 0; i < time; i++) {
            new BIOClient().startConnect();
        }
    }
}

缺点

  • 上面服务端和客户端实际上是伪异步。表面上看起来不会因为客户端的增加导致内存溢出。但是因为实际上还是accpet同步阻塞等待。所以在连接性能上还是不好。

  • 我们在ServerHanndler中是读取客户端传输的数据通过BufferedReader.readLine这个方法。我们跟踪下去发现实际调用的是InputStream.read这个方法。

002.jpg


/**
  * Reads the next byte of data from the input stream. The value byte is
  * returned as an <code>int</code> in the range <code>0</code> to
  * <code>255</code>. If no byte is available because the end of the stream
  * has been reached, the value <code>-1</code> is returned. This method
  * blocks until input data is available, the end of the stream is detected,
  * or an exception is thrown.
  *
  * <p> A subclass must provide an implementation of this method.
  *
  * @return     the next byte of data, or <code>-1</code> if the end of the
  *             stream is reached.
  * @exception  IOException  if an I/O error occurs.
  */
public abstract int read() throws IOException;

  • 我们在翻阅他们的源码说明,会发现上面注释大意为:当没有字节的时候意味着已经结束了我们会返回-1。这个方法一直阻塞知道获取到字节或者是结束或者是抛出异常。我们之前也说了服务端等待连接的时候是阻塞式等待。这会造成客户端连接的一些问题。但是客户端连接上以后开始通信了。服务端获取客户端的消息也是采用阻塞式等待的。这会直接造成交互等待从而造成交互拥堵。换句话说客户端A发送了100条消息。服务端会一条一条处理。像食堂打菜排队一样。
  • 同样的道理OutputStream也是阻塞式的。这里有兴趣的读者可以自行翻阅源码查看。这也说明我们连接是阻塞的。通信也是阻塞的。这就是BIO暴露的缺点。
  • 因为阻塞,会造成后期客户端的接入无法成功,会一直等待。造成连接超时。

NIO

NIO=Non Block IO .即非阻塞式编程。

  • 在BIO中我们通过ServerSocket、Socket实现服务端和客户端的开发。在NIO中对应提供了ServerSocketChannel、SocketChannel两个类实现通信。这两个类支持阻塞式和非阻塞式模式。阻塞模式这里不做介绍造成的后果由和BIO一样。下面我们来看看如何实现NIO

##ByteBuffer

  • ByteBuffer使我们NIO通信的一个缓冲区,我们的读写都是借助与它传递的。因为他提供了类似指针指向,我们操作指向就可以获取到字节。

##Channel

  • 在NIO中他被看做是一个通道。通过Channel控制读和写。BIO中是通过Stream方式传递的。Channel和Stream相比最大的特点Channel是双向的。简单的理解就是读用Channel,写也用Channel。在BIO中InputStream和OutputStream分别负责读和写。上面提到的ServerSocketChannelSocketChannel都是Channel的子类。

##Selector

-多路复用器。这里我们可以理解为注册中心。所有的handler再向selector注册的时候会带上标签(SelectorKey)。在某个Channel发生读或写的事件时这个Channel会处于就绪状态。在Selector轮询的时候会筛选出来。然后我们在根据SelectorKey判断监听的是什么事件。从而做出处理。查阅资料得知selector没有连接限制。理论上一个selector可以管理N个Channel。

003.jpg

  • 上面是服务器的开发流程。其实就是用一个线程来管理selector。然后不停遍历selector然后捕获事件。服务端和客户端捕获不同的事件。下面大概列举了下事件

004.jpg

  • 框架搭建就是流程上的,剩下的就是我们在selectorKey这个事件上进行业务的io处理。在读写期间就是ByteBuffer来实现。NIO的实现和BIO相对比较复杂。

进化

关于NIO2.0 和nettry 他们在次基础上进行提升!不得不说现在基本上是netty的天下了。下章节我们针对netty来展开讨论!今天端午节不说了我吃粽子去了

记得吃完粽子,回来点个赞哦!!!