netty学习笔记-java的BIO、NIO与AIO

BIO

网络IO中比较经典的场景就是http服务器,在java中通过socket也就是BIO实现过程大致如下:

1.创建一个ServerSocket监听一个端口

2.通过accept方法阻塞服务器并等待客户端的连接

3.客户端发起请求,服务器通过accept方法获取一个客户端的socket

4.启动一个新线程来处理我们客户端的请求

5.处理请求的线程通过socket获取输入流并读取流中的数据

6.获取字节数据根据http协议解码数据,获取http请求

7.处理http请求,根据请求构建响应数据

8.根据http的编码协议对响应数据编码并写入客户端的socket

9.socket调用系统write函数将数据发送到物理设备网卡返回给客户端

10.重复接收请求从第3步执行

大概代码实现如下:

package com.crs.echo;
​
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
​
/**
 * @author administrator
 * @version 1.0
 * @date 2021/7/2 22:29
 **/
public class BIOServer implements Server{
    @Override
    public void start(int port) throws IOException {
        // 创建serversocket并监听端口
        final ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("打开socket连接并绑定端口"+port);
        try {
            while (true){
                // 利用accept函数阻塞并接收前端请求
                Socket socket =serverSocket.accept();
                System.out.println("接收到请求来自: " + socket.getInetAddress());
                // 有请求到来时开启新线程处理请求
                new Thread(()->{
                    OutputStream out;
                    InputStream in;
                    try {
                        // socket的输出流,向socket写入数据
                        out = socket.getOutputStream();
                        // socket的输入流,从socket中读取数据
                        in = socket.getInputStream();
                        DataInputStream dis = new DataInputStream(in);
                        // 读取请求头,http的请求头:Get / HTTP/1.1
                        int len;
                        String response = "";
                        byte[] bytes = new byte[124];
                        while ((len=dis.read(bytes))!=-1&&dis.available()>0){
                            response=response+new String(bytes);
                        }
                        System.out.println("准备将数据写回前端,返回的数据为");
                        System.out.println(response);
                        // 通过OutputStream构建字符输出流
                        PrintWriter writer = new PrintWriter(socket.getOutputStream());
                        // 利用字符输出流向前端返回数据
                        writer.println("HTTP/1.0 200 OK");
                        writer.println("Content-Type:application/json");
                        writer.println();
                        writer.println(response);
                        // 将流中的数据从缓冲区刷新到内存中
                        writer.flush();
                        // 关闭流
                        writer.close();
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}
​

上述代码描述的就是使用BIO的形式来实现http服务器的过程,缺点是每当有新的请求时服务器都要创建一个新的线程来处理请求,创建线程是一个比较消耗资源的事情

NIO

在NIO模型中不需要创建多个线程,利用操作系统的IO多路复用模型来建立连接和读取数据

1.创建一个服务器socket通道并绑定监听端口

2.创建选择器Selector

3.将服务器socket通道注册到Selector中

4.通过选择器选择就绪事件并处理

5.通过就绪事件的通道键值获取客户端socket通道

6.从客户端socket通道中读取数据并处理

7.处理完数据后将响应数据写回客户端socket通道

8.在数据处理过程必须使用缓冲区ByteBuffer

通过java的NIO模式编写一个回显服务器

package com.crs.echo;
​
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
​
/**
 * @author 刘小江
 * @version 1.0
 * @date 2021/8/10 23:26
 **/
public class NIOServer implements Server{
    @Override
    public void start(int port) throws IOException {
        // 初始化服务器socket通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 绑定主机端口
        serverSocketChannel.socket().bind(new InetSocketAddress("localhost", port));
        // 设置通道为非阻塞的
        serverSocketChannel.configureBlocking(false);
​
        // 创建selector
        Selector selector =Selector.open();
        // 将通道的连接事件注册到selector中
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 通过无线循环处理连接
        while (true){
            // selector选择就绪事件,此处会阻塞
            int num = selector.select();
            if(num == 0){
                continue;
            }
            // 获取到已准备就绪的通道的选择键值,返回的是一个就绪事件集合
            Set<SelectionKey> selectKeys=selector.selectedKeys();
            // 迭代处理就绪事件
            Iterator iter = selectKeys.iterator();
            while (iter.hasNext()){
                SelectionKey key = (SelectionKey) iter.next();
                iter.remove();
                // 处理连接请求
                if(key.isAcceptable()){
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    // 将客户端socket通道注册到读事件中
                    socketChannel.register(selector,SelectionKey.OP_READ);
                }
                // 处理读通道请求
                else if(key.isReadable()){
                    SocketChannel channel = (SocketChannel) key.channel();
                    // 创建缓冲区默认为写模式
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    // 将通道中的数据读取到缓冲区中
                    channel.read(buffer);
                    // 翻转缓冲区将缓冲区切换为读模式
                    buffer.flip();
                    byte[] bts = new byte[buffer.limit()];
                    buffer.get(bts);
                    System.out.println("接收到的前端数据为"+new String(bts,"utf8"));
                    // 构建返回的数据
                    // 构建请求头响应码
                    String head1="HTTP/1.0 200 OK"+"\r\n";
                    // 构建请求头返回数据格式
                    String head2="Content-Type:application/json"+"\r\n";
                    // 构建返回的数据为前端请求的数据
                    String content = new String(bts,"utf8");
                    String resStr = head1 +"\r\n"+ head2+"\r\n"+content;
                    buffer = ByteBuffer.allocate(resStr.length());
                    buffer.put(head1.getBytes());
                    buffer.put(head2.getBytes());
                    // http协议中响应头和响应数据中有一个空行
                    buffer.put("\r\n".getBytes());
                    buffer.put(content.getBytes());
                    buffer.flip();
                    channel.write(buffer);
                    channel.close();
                }
            }
        }
    }
}

AIO

package com.crs.echo;
​
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
​
/**
 * @author 刘小江
 * @version 1.0
 * @date 2021/8/15 13:47
 **/
public class AIOServer implements Server{
    @Override
    public void start(int port) throws IOException {
        // 打开aio套接字通道
        AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
        // 绑定端口
        serverSocketChannel.bind(new InetSocketAddress(port));
        System.out.println("服务器打开连接,端口为:"+port+",等待客户端的连接");
        while (true){
            // 服务器等待连接
            Future<AsynchronousSocketChannel> accept = serverSocketChannel.accept();
            try {
                AsynchronousSocketChannel socketChannel = accept.get();
                System.out.println("服务器与" + socketChannel.getRemoteAddress() + "建立连接");
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                Future<Integer> read = socketChannel.read(buffer);
                while (!read.isDone()){
                    // 此处可以继续执行其他的业务逻辑
                    Thread.sleep(10);
                }
                // 数据准备好后线程继续处理网络数据
                buffer.flip();
                byte[] bts = new byte[buffer.limit()];
                buffer.get(bts);
                System.out.println("接收到的前端数据为"+new String(bts,"utf8"));
​
                // 构建返回的数据
                // 构建请求头响应码
                String head1="HTTP/1.0 200 OK"+"\r\n";
                // 构建请求头返回数据格式
                String head2="Content-Type:application/json"+"\r\n";
                // 构建返回的数据为前端请求的数据
                String content = new String(bts,"utf8");
                String resStr = head1 +"\r\n"+ head2+"\r\n"+content;
                buffer = ByteBuffer.allocate(resStr.length());
                buffer.put(head1.getBytes());
                buffer.put(head2.getBytes());
                // http协议中响应头和响应数据中有一个空行
                buffer.put("\r\n".getBytes());
                buffer.put(content.getBytes());
                buffer.flip();
                Future<Integer> write = socketChannel.write(buffer);
                while (!write.isDone()){
                    Thread.sleep(0);
                }
                socketChannel.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
​
    }
}

BIO指的是同步阻塞IO,就是在读取数据时必须阻塞,一个线程只能处理一个连接,要实现多连接时就必须利用多线程的方式来处理,但是在IO的这一块的处理只能是阻塞的方式,不能高效的利用计算机资源,且连接数过多创建更多线程时也需要消耗大量计算机资源

NIIO指的是同步非阻塞IO,在socket建立连接时不需要阻塞,通过selector选择来轮询就绪事件,多个连接无需再创建多个线程,读取数据方面通过通道channel和缓冲区bytebuffer来读取不用进行数据阻塞,在内核交互角度来说BIO和NIO都是同步的,在编程角度来说同步阻塞的IO的操作就是执行连接和读取数据都只能顺序完成,在执行IO操作时程序不能执行其他事情只能等待,而使用非阻塞的操作则在内核执行IO操作时应用程序可以先做其他事情

AIO指的是异步IO,异步IO下不再是应用程序去向系统内核要数据了,而是内核将数据准备好之后通知应用进程,因此在执行过程中也就没有了阻塞的概念,异步IO在程序代码上的体现就是用socketChannel.read读取数据时是一个异步操作,内核立即返回给你一个结果,数据没有准备好时你可以先做其他事情,内核告诉你数据准备好了你再继续处理

需要强调的是我们的java程序和操作系统之间还隔着java虚拟机,在理解IO模型的时候需要考虑IO模型是针对jvm的操作模型,而jvm封装对IO模型的使用并提供给我们比较方便使用的api

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值