Netty(1)-BIO、NIO与Netty入门

一.BIO下的client-server通信模式

BIO即Blocking IO,同步阻塞式IO,在BIO的通信模式下,
「一个线程只能在一个accept、read或write方法中阻塞,对应着一条IO连接线路」
在这里插入图片描述
以下程序即BIO下的client对server的通信模式:

  • BIOClient
/**
 * @Auther: ARong
 * @Description: BIO 的 客户端
 */
public class BIOClient {
    public static void main(String[] args) throws IOException, InterruptedException {
        Socket socket = new Socket("127.0.0.1", 6666);
        // 每2s向服务端发送helloword
        for (int i = 0; i < 10; i++) {
            Thread.sleep(2*1000);
            String message = String.format("HelloWorld From %s", new SimpleDateFormat("hh:mm:ss").format(new Date()));
            socket.getOutputStream().write(message.getBytes());
        }
    }
}
  • BIOServer
/**
 * @Auther: ARong
 * @Description: BIO 的 服务端
 */
public class BIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(6666);
        while (true) {
            // 阻塞直到有客户端连接
            Socket socket = serverSocket.accept();
            // 每次有连接即创建一个线程接受处理
            new Thread(()->{
                // 处理请求
                int len = -1;
                byte[] data = new byte[1024];//每次读取1kb
                try {
                    InputStream inputStream = socket.getInputStream();
                    while ((len = inputStream.read(data)) != -1) {
                        System.out.println(new String(data, 0, len));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

传统的BIO模型每个连接创建成功都需要一个线程来维护,每个线程包含一个while死循环,那么1w个连接对应1w个线程,继而1w个while死循环带来如下几个问题:

  1. 线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统耗不起
  2. 线程切换效率低下:单机CPU核数固定,线程爆炸之后操作系统频繁进行线程切换,应用性能急剧下降
  3. 数据读写是以字节流为单位效率不高:每次都是从操作系统底层一个字节一个字节地读取数据

二.NIO下的通信模式

NIO即New IO或NoBloking-IO,同步非阻塞式IO,它是基于Buffer、Channel和Selector的「IO多路复用通信模式,一个线程不会在accpet、read或write方法中阻塞,而是可以同时处理多个IO连接线路」
在这里插入图片描述
NIO相较于BIO的优点有:

  1. 只需要使用少量的线程,通过IO多路复用的方式即可处理多个IO连接,而不需要阻塞在某一IO连接中,也就不会存在线程上下文切换问题,提高了通信效率
  2. 数据读写不再以字节流为单位,而是以缓冲块为单位进行读写

以下是Java实现的NIO Demo

  • NIOServer(Copy From 闪电侠)
public class NIOServer {

    /**
     * serverSelector负责轮询是否有新的连接,clientSelector负责轮询连接是否有数据可读.
     * 服务端监测到新的连接不再创建一个新的线程,而是直接将新连接绑定到clientSelector上,这样不用IO模型中1w个while循环在死等
     * clientSelector被一个while死循环包裹,如果在某一时刻有多条连接有数据可读通过 clientSelector.select(1)方法轮询出来进而批量处理
     * 数据的读写以内存块为单位
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        Selector serverSelector = Selector.open();
        Selector clientSelector = Selector.open();

        new Thread(() -> {
            try {
                ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                serverSocketChannel.socket().bind(new InetSocketAddress(8000));
                serverSocketChannel.configureBlocking(false);
                serverSocketChannel.register(serverSelector, SelectionKey.OP_ACCEPT);

                while (true) {
                    // 轮询监测是否有新的连接
                    if (serverSelector.select(1) > 0) {
                        Set<SelectionKey> selectionKeys = serverSelector.selectedKeys();
                        Iterator<SelectionKey> keyIterator;
                        keyIterator = selectionKeys.iterator();
                        while (keyIterator.hasNext()) {
                            SelectionKey selectionKey = keyIterator.next();
                            if (selectionKey.isAcceptable()) {
                                try {
                                    //(1)每来一个新连接不需要创建一个线程而是直接注册到clientSelector
                                    SocketChannel socketChannel = ((ServerSocketChannel) selectionKey.channel()).accept();
                                    socketChannel.configureBlocking(false);
                                    socketChannel.register(clientSelector, SelectionKey.OP_READ);
                                } finally {
                                    keyIterator.remove();
                                }
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                while (true) {
                    // (2)批量轮询是否有哪些连接有数据可读
                    if (clientSelector.select(1) > 0) {
                        Set<SelectionKey> selectionKeys = serverSelector.selectedKeys();
                        Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                        while (keyIterator.hasNext()) {
                            SelectionKey selectionKey = keyIterator.next();
                            if (selectionKey.isReadable()) {
                                try {
                                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                                    //(3)读取数据以块为单位批量读取
                                    socketChannel.read(byteBuffer);
                                    byteBuffer.flip();
                                    System.out.println(Charset.defaultCharset().newDecoder().decode(byteBuffer)
                                            .toString());
                                } finally {
                                    keyIterator.remove();
                                    selectionKey.interestOps(SelectionKey.OP_READ);
                                }
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

三.Netty入门

1. 什么是Netty

Netty 是一个基于 Java NIO 类库的异步通信框架,它的架构特点是:异步非阻塞、基于事件驱动、高性能、高可靠性和高可定制性。使用Netty可以很好地替代掉繁琐的、难以使用的Java NIO类库,并且Netty提供了更多可用的功能。

2.Netty的实际应用

  1. 高性能HTTP服务器
  2. 高性能RPC框架(例如Dubbo)
  3. Redission(Redis For Java)
  4. IM即时通信应用
  5. 大量需要网络通信的框架底层实现

3.Netty入门Demo

  1. 导入maven依赖
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.42.Final</version>
</dependency>
  1. NIOServer
public class NIOServer {
    public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(); // 监听组
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(); // 工作组

        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class) //NIO模式
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    // 初始化channel
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
                                System.out.println(String.format("服务端接收到消息:%s", msg));
                            }
                        });
                    }
                }).bind(8000);
    }
}
  1. NIOCLient
public class NIOClient {
    public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup group = new NioEventLoopGroup();

        bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new StringEncoder());
            }
        });

        Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
        while (true) {
            String message = String.format("HelloWorld From %s", new SimpleDateFormat("hh:mm:ss").format(new Date()));
            channel.writeAndFlush(message);
            Thread.sleep(2000);
        }
    }
}

执行程序:

在这里插入图片描述

发布了309 篇原创文章 · 获赞 205 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/pbrlovejava/article/details/104148009