关于java的io

简介

本篇文章是为了学习netty作为铺垫,同时也为了了解java的io模型。
本项目源码github地址:https://github.com/itwwj/netty-learn.git 中的 netty-day01-io项目

1. java的io分类

java的io分为bio、nio和aio。
BIO 同步阻塞I/O模式,全称Block-IO 是一种同步阻塞的io模型。并发处理能力低,通信耗时。

NIO同步非阻塞模式,全程 Non-Block IO ,是Java SE 1.4版以后,针对网络传输效能优化的新功能。是一种非阻塞同步的通信模式。NIO 与原来的 I/O 有同样的作用和目的, 他们之间最重要的区别是数据打包和传输的方式。原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。面向流的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。面向块的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O - 缺少一些面向流的 I/O 所具有的优雅性和简单性。

AIO异步非阻塞I/O模型,全程 Asynchronous IO,是异步非阻塞的IO。是一种非阻塞异步的通信模式。在NIO的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。

简单的来比较就是:

bio:
发起请求————>阻塞等待————>处理完成

nio:
发起请求——>单线程selector轮询channel——>处理请求——>处理完成

aio:
发起请求————————————>通知回调
其中aio是最理想的io模型,不过很遗憾linux暂不支持aio

2.tcp三次握手和四次挥手

三次握手:

第一次握手: 客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。

第二次握手: 服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。

第三次握手: 客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
在这里插入图片描述

四次挥手

第一次挥手: A数据传输完毕需要断开连接,A的应用进程向其TCP发出连接释放报文段(FIN = 1,序号seq = u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1状态,等待B的确认。

第二次挥手: B收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),B进入CLOSE-WAIT关闭等待状态,此时的TCP处于半关闭状态,A到B的连接释放。而A收到B的确认后,进入FIN-WAIT-2状态,等待B发出的连接释放报文段。

第三次挥手: 当B数据传输完毕后,B发出连接释放报文段(FIN = 1,ACK = 1,序号seq = w,确认号ack=u+1),B进入LAST-ACK(最后确认)状态,等待A 的最后确认。

第四次挥手: A收到B的连接释放报文段后,对此发出确认报文段(ACK = 1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSE状态。
在这里插入图片描述

3.使用io实现tcp服务端

3.1 bio实现

bio作为一种阻塞io效率低,但实现简单,经常和多线程配合使用

代码实现:

 ServerSocket socket = new ServerSocket(9090);
        while (true) {
            Socket accept = socket.accept();
            new Thread(() -> {
                InputStream is = null;
                try {
                    is = accept.getInputStream();
                    int size = is.read();
                    byte[] bytes = new byte[size];
                    is.read(bytes);
                    System.out.println(“收到消息:”+new String(bytes));
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

3.2 nio实现

public class NioServer {
    public static void main(String[] args) throws IOException {
        //创建serverSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //创建selector
        Selector selector = Selector.open();
        //绑定端口1100
        serverSocketChannel.socket().bind(new InetSocketAddress(1100));
        //设置成非阻塞
        serverSocketChannel.configureBlocking(false);
        //将serverSocketChannel注册到selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //循环等待客户端连接
        while (true) {
            //阻塞1s如果没有事件就跳出本次循环
            if (selector.select(1000) == 0) {
                continue;
            }
            //如果返回>0,表示以获取到关注的事件 获取selectionKeys集合
            //selectedKeys获取有事件发生的SelectionKey!!!
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key=iterator.next();
                //判断当前的事件是否是key的注册事件
                if (key.isAcceptable()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //设置为非阻塞
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("收到连接。。。" + serverSocketChannel.hashCode());
                    //判断当前的事件是否是当前SelectionKey的读事件
                } else if (key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                    channel.read(byteBuffer);
                    System.out.println("服务端收到:" + new String(byteBuffer.array()));
                }
                iterator.remove();
            }
        }
    }
}

在这里插入图片描述

3.3 aio实现

java aio实现:
启动类:

public class AioServer {
    private static AioServerHandler serverHandler;
    public static int clientCount = 0;

    public static void start() {
        if (serverHandler != null) {
            return;
        }
        serverHandler = new AioServerHandler(1100);
        new Thread(serverHandler, "Server").start();
    }

    public static void main(String[] args) {
        AioServer.start();
    }
}

AioServerHandler :

public class AioServerHandler implements  Runnable {

    public CountDownLatch latch;
    //异步通信通道
    public AsynchronousServerSocketChannel channel;

    public AioServerHandler(int port){
        try {
            //建立服务端通道
            channel=AsynchronousServerSocketChannel.open();
            //绑定端口
            channel.bind(new InetSocketAddress(port));
            System.out.println("server is start,port:"+port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        latch=new CountDownLatch(1);
        //接收客户端连接
        channel.accept(this,new AioAccepHandler());
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

AioReadHandler :

public class AioReadHandler implements CompletionHandler<Integer, ByteBuffer> {
    private AsynchronousSocketChannel channel;

    public AioReadHandler(AsynchronousSocketChannel channel) {
        this.channel = channel;
    }

    /**
     * 读取到消息后的处理
     *
     * @param result
     * @param attachment
     */
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        if (result == -1) {//如果条件成立,说明客户端主动终止了tcp套接字,这时服务端终止就可以了
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        attachment.flip();
        byte[] bytes = new byte[attachment.remaining()];
        attachment.get(bytes);
        String msg;
        try {
            System.out.println(result);
            msg = new String(bytes, "UTF-8");
            System.out.println("server accept message:" + msg);
            String response = Ch01Const.response(msg);
            doWrite(response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doWrite(String result) {
        byte[] bytes = result.getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        writeBuffer.put(bytes);
        writeBuffer.flip();
        channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                if (attachment.hasRemaining()) {//如果没写完
                    channel.write(attachment, attachment, this);
                } else { //如果写完 读取数据
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    //异步读取数据
                    channel.read(readBuffer,readBuffer,new AioReadHandler(channel));
                }
            }
            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            this.channel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

AioAccepHandler :

public class AioAccepHandler implements CompletionHandler<AsynchronousSocketChannel, AioServerHandler> {

    @Override
    public void completed(AsynchronousSocketChannel result, AioServerHandler attachment) {
        AioServer.clientCount++;
        System.out.println("链接的客户端数:" + AioServer.clientCount);
        //重新注册监听,让别的客户端也可以连接
        attachment.channel.accept(attachment, this);
        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
        //异步读取
        //1)ByteBuffer dst:接收缓冲区,用于从异步Channel中读取数据包;
        //2)  A attachment:异步Channel携带的附件,通知回调的时候作为入参使用;
        //3)  CompletionHandler<Integer,? super A>:系统回调的业务handler,进行读操作
        result.read(readBuffer,readBuffer,new AioReadHandler(result));

    }

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

4.nio 流程解读

在这里插入图片描述

  1. 当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel

  2. Selector 进行监听 select 方法, 返回有事件发生的通道的个数.

  3. 将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel

  4. 注册后返回一个 SelectionKey, 会和该Selector 关联(集合)

  5. 进一步得到各个 SelectionKey (有事件发生)

  6. 在通过 SelectionKey 反向获取 SocketChannel , 方法 channel()

  7. 可以通过 得到的 channel , 完成业务处理

本项目源码github地址:https://github.com/itwwj/netty-learn.git 中的 netty-day01-io项目
下一篇:
io模型及nio进阶

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值