JAVA网络通信IO-NIO-AIO-Netty

Socket实现网络通信(阻塞式IO)

阻塞式IO在两个地方会阻塞

  • 在使用IO和Socket构造网络服务时 接收连接:accept(),接收请求数据,发送响应数据都可能引起阻塞的操作。(Handler必须使用多线程异步操作,不然别的连接进不来)
  • 线程从Socket输入流读数据时,如果没有足够的数据就会进入阻塞状态,直到读够了足够的数据,或者达到输入流的末尾,或者出现了异常,才能从输入流的read()方法返回或异常中断。
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket();
        ss.bind(new InetSocketAddress("127.0.0.1", 8888));
        while(true) {
            Socket s = ss.accept(); //阻塞方法

            new Thread(() -> {
                handle(s);
            }).start();
        }

    }

    static void handle(Socket s) {
        try {
            byte[] bytes = new byte[1024];
            int len = s.getInputStream().read(bytes);
            System.out.println(new String(bytes, 0, len));

            s.getOutputStream().write(bytes, 0, len);
            s.getOutputStream().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

NIO对网络通信改进

网络通信在阻塞模式下,

  1. read()方法会争取读到n个字节,如果输入流中不足n个字节,就进入阻塞状态,直到读取了n个字节,或者读到了输入流末尾,或者出现了I/O异常。
  2. socket.accept()方法如果没有接收到连接,也会一直等待

大量线程连接进来的时候,效率比较低,所有线程都阻塞在接收和read数据的地方

网络通信在非阻塞模式下(NIO对BIO的改进)

  1. read()方法奉行能读到多少数据就读到多少数据的原则。read()方法读取当前通道中的可读数据,有可能不足n个字节,或者为0个字节,read()方法总会立刻返回。而不会等到读取了n个字节才返回,read()方法返回实际上读入的字节数。SocketChannel extends AbstractSelectableChannel 类的中 int read(ByteBuffer dst)方法是非阻塞式的。
  2. ServerSocketChannel或SockeChannel通过register()方法向Selector注册事件时register()方法会创建一个SelectionKey对象,这个SelectionKey对象是跟踪注册事件的句柄。在SelectionKey对象有效期,Selector会一直监控与SelectorKey对象相关的事件,如果事件发生,就会把SelectionKey对象加入到Selector-keys集合中。

1)NIO-Single(单线程模型)

大管家selector

除了管理客户端的连接之外

连接通道建立后:还盯着有没有需要读写的数据(一个大管家 领着一帮工人)

使用Selector创建一个非阻塞的服务器。

    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8888));
        ssc.configureBlocking(false);

        System.out.println("server started, listening on :" + ssc.getLocalAddress());
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while(true) {
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            while(it.hasNext()) {
                SelectionKey key = it.next();
                it.remove();
                handle(key);
            }
        }

    }

handler(key)没有异步的去处理,这是单线程模型的NIO

NIO的读写都是用ByteBuffer 一块一块的读 相比BIO的一个byte的读,提高了很多效率 但是特别难用:推荐NIO类库介绍

当你忘记flip复位的操作,你可以把消息读成一半,这也是Netty受欢迎的原因。

2)NIO-Reactor(多线程模型)

Selector任务是BOSS 不干别的事,就负责客户端的连接

要不要写 交给Worker工人来做,工人是一个池子(线程池)

 

也就是NIO单线程模型中 Handler用线程池调用

AIO对网络通信改进

AIO是类似观察者模式的事件回调,而不在需要轮循

当客户端需要连接的时候 交给操作系统去链接

操作系统一旦连接上了客户端,会给大管家Selector说有人要连上来了

大管家只负责连接的功能 而不需要轮循环,连好的通过交给工人worker处理通道里面的信息

参考文章:AIO

public class Server {
    public static void main(String[] args) throws Exception {
        final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress(8888));

        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Object attachment) {
                serverChannel.accept(null, this);
                try {
                    System.out.println(client.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer attachment) {
                            attachment.flip();
                            System.out.println(new String(attachment.array(), 0, result));
                            client.write(ByteBuffer.wrap("HelloClient".getBytes()));
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            exc.printStackTrace();
                        }
                    });


                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });

        while (true) {
            Thread.sleep(1000);
        }

    }
}

serverChannel.accept的时候 就可以走了,回调函数 观察者设计模式,把这个CompletionHandler方法交给操作系统去执行

下面之所以写while(true)是为了防止程序结束,如果想写的严谨一些,可以用countDownLatch

Netty

Netty是对NIO进行了封装,封装的API更像AIO

netty的写法和AIO差不多

netty把NIO中难用的byteBuffer封装的特别好

疑问点:有了AIO为什么还需要NIO

因为AIO和NIO在linux底层都是用的epoll模型实现的,epoll本身就是轮循模型

所以你上层在怎么封装,下层还是轮循(Netty就是用的NIO而不是AIO),在Linux中AIO的效率未必比NIO高

而Windows的AIO是自己单独实现的,不是轮训模型而是事件模型(Windows的Server比较少 Netty未做重点)

Netty服务端

public class HelloNetty {
    public static void main(String[] args) {
        new NettyServer(8888).serverStart();
    }
}

class NettyServer {
    int port = 8888;
    public NettyServer(int port) {
        this.port = port;
    }
    public void serverStart() {
        /**定义两个线程池**/
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap b = new ServerBootstrap();
        /**把这两个group传给Server启动的封装类**/
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)/**指定Server启动之后 客户端连接上来的通道类型**/
                .childHandler(new ChannelInitializer<SocketChannel>() {/**每一个客户端连上来之后 监听器处理**/
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        /**通道一旦init 在这个通道上就添加对这个通道的处理器**/
                        ch.pipeline().addLast(new Handler());
                    }
                });

        try {
            ChannelFuture f = b.bind(port).sync();

            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }

    }
}

class Handler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //super.channelRead(ctx, msg);
        System.out.println("server: channel read");
        ByteBuf buf = (ByteBuf)msg;
        System.out.println(buf.toString(CharsetUtil.UTF_8));
        ctx.writeAndFlush(msg);
        ctx.close();
        //buf.release();
    }

    /**发生异常的回调方法**/
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //super.exceptionCaught(ctx, cause);
        cause.printStackTrace();
        ctx.close();
    }
}

BossGroup:只负责客户端的连接过程

WorkerGroup:只负责数据的处理

Netty客户端

package com.mashibing.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.ReferenceCountUtil;

public class Client {
    public static void main(String[] args) {
        new Client().clientStart();
    }

    private void clientStart() {
        EventLoopGroup workers = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(workers)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        System.out.println("channel initialized!");
                        ch.pipeline().addLast(new ClientHandler());
                    }
                });

        try {
            System.out.println("start to connect...");
            ChannelFuture f = b.connect("127.0.0.1", 8888).sync();

            f.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();

        } finally {
            workers.shutdownGracefully();
        }

    }


}

class ClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel is activated.");

        final ChannelFuture f = ctx.writeAndFlush(Unpooled.copiedBuffer("HelloNetty".getBytes()));
        f.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                System.out.println("msg send!");
                //ctx.close();
            }
        });


    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            ByteBuf buf = (ByteBuf)msg;
            System.out.println(buf.toString());
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }
}
View Code

同步异步关注的是消息通信机制

阻塞非阻塞关注的是等待消息时的状态

同步阻塞 IO :

在此种方式下,用户进程在发起一个 IO 操作以后,必须等待 IO 操作的完成,只有当真正完成了 IO 操作以后,用户进程才能运行。

JAVA传统的 IO 模型属于此种方式!

同步非阻塞 IO:

在此种方式下,用户进程发起一个 IO 操作以后 边可 返回做其它事情,但是用户进程需要时不时的询问 IO 操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的 CPU 资源浪费。

其中目前 JAVA 的 NIO 就属于同步非阻塞 IO 。

参考文章CSDN:https://blog.csdn.net/lovewebeye/article/details/105502035

posted @ 2018-09-06 20:02  palapala  阅读(1501)  评论(0编辑  收藏  举报