BIO、NIO学习

1.同步与异步

同步与异步是应用程序和操作系统对IO事件的处理。

同步:程序在处理IO事件时必须阻塞在某个方法上等待IO完成(IO阻塞事件或轮询IO事件),通俗来说,也就是一次只能完成一个连接的读写事件,不能同时完成其它读写事件,而且一件事件的完成必须依赖另一件事件的完成,只有等待被依赖事件完成后,依赖事件才算完成,比如客户端向服务器发送数据,必须等待服务器端的读事件完成,客户端的写事件才算完成,这批操作要么都成功,要么都失败,两个事件的状态保持一致,这是一种可靠的任务序列。

异步:程序在处理IO事件时,不需要正真完成IO事件,给程序一个通知就行,依赖事件不需要等待被依赖事件的完成,只要完成自身的事件就算完成,依赖事件无法确定被依赖事件是否完成,这是一种不可靠的任务序列。

2.阻塞和非阻塞

阻塞和非阻塞是应用程序访问数据时,对数据是否准备就绪的处理方式。

阻塞:程序访问数据时,数据准备就绪,程序直接返回,数据未准备就绪,程序则处于等待状态,比如多个客户端请求服务器端,当某一个客户端向服务器发送数据,但数据仍然在准备,程序会处于读事件的等待中,直到这个读事件完成,其它客户端才能访问服务器。

非阻塞:不管数据是否准备就绪,程序直接返回,像刚才的例子中,即便数据仍在准备,但其它客户端可以继续访问服务器,执行各自的连接事件。

小结:
同步与异步主要是针对某个连接中,数据的读写是否同步。
阻塞与非阻塞主要指服务端和多个连接中是否存在阻塞。

3.BIO

3.1 BIO的概念

BIO是Blocking IO的缩写,它基于java.io包,是Java推出之初的IO操作模块,它是基于流式模式实现的,交互方式是同步阻塞的方式。

3.2 BIO的特点

程序的IO操作的完成的状态是一致的,在读写动作完成前,线程会一直处于阻塞的状态。它的优点是简单直观,缺点是效率和拓展性都很低。

3.3 BIO的重要组件

Socket和ServerSocket是BIO的最重要的组件,Socket套接字代表的是客户端对象,同时也是交互通道,而ServerSocke是服务器对象,同时绑定IP地址和端口号。通过ServerSocke的accept()完成三次握手,表示接收Socket通道,最终从Socket中获取字节IO流完成数据的交互。
BIO组件

3.4 BIO的特点展示和底层原理

刚才说到BIO的特点是:
a.程序的IO操作的完成的状态是一致的,
b.在读写动作完成前,线程会一直处于阻塞的状态。
下面用两个案例展示阻塞IO的特点。

a.连接事件的状态一致,读写动作要么都成功,要么都失败

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 10000);
        PrintStream ps = new PrintStream(socket.getOutputStream());
        ps.println("同时成功");
        ps.flush();
        ps.close();
    }
}
public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(10000);
        Socket socket = serverSocket.accept();
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        System.out.println("Client:" + br.readLine());
        br.close();
    }
}

在这里插入图片描述
b.在读写动作完成前,线程会一直处于阻塞的状态

下面是多个客户端请求服务器的案例:

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(10000);
        while (true) {
            Socket socket = serverSocket.accept();
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + ":" + line);

            }
            br.close();
        }
    }
}

public class Client1 {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("127.0.0.1", 10000);
        Scanner sc = new Scanner(System.in);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        System.out.println("请说:");
        bw.write(sc.nextLine());
        bw.close();
    }
}

public class Client2 {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("127.0.0.1", 10000);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("Hello");
        bw.close();
    }
}

控制台结果:
在这里插入图片描述
针对此案例,其底层原理是这样的:
BIO底层原理
服务器接收客户端的数据,并不是一开始就来到服务器端的,而是首先来到终端的网卡上面,然后再来到操作系统的内核中,最后才会到达服务器。就拿接收事件举例,调用accept(),内核便会执行recvFrom(Block…)方法,进行数据准备,将数据(Socket)从网卡发送到内核空间中,最终将数据拷贝到用户空间中,这样就完成接收事件。其它的连接事件的原理也是一样的。

在此案例中,我们先启动Client1再启动Client2会发现,即便Client2的写事件完成,可是服务器端依然没有收到Client2的发送数据,原因是Client1程序的写事件数据没有准备好,使服务器一直阻塞在读事件read(读Client1的数据)中,程序一直处于等待状中,所以无办法接收Client2的数据。

只要在Client1的控制台输入数据,服务器就不会阻塞,自然也会收到Client2的发送的数据,结果如下图:

在这里插入图片描述

3.5 解决BIO的阻塞问题

解决BIO阻塞的常用方法有两种(下面通过两个案例展示):
1).通过多线程异步的原理,每创建一次连接就分配一个独立的线程来处理连接事件,即一个客户端对应一个线程。
2).通过线程池,同样是利用异步方式解决阻塞,也是一个客户对应一个线程。

案例1:

// 服务器端程序
public class ServerDemo3 {
    public static void main(String[] args) throws Exception {
            ServerSocket serverSocket = new ServerSocket(10004);
            while (true) {
                Socket socket = serverSocket.accept();
                // 每创建一次连接,分配一个独立线程来处理连接事件
                new Thread(new MyRunnable(socket)).start();
            }
        }
}
// 线程
class MyRunnable implements Runnable {
    private Socket socket;

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

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println("客户端" + socket.getRemoteSocketAddress() + "说:" + line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// 客户端A
class ClientDemo2 {
    public static void main(String[] args) throws Exception {
        // 1.创建客户端Socket对象,输入服务器端的IP地址和端口号,请求连接服务器端
        Socket socket = new Socket("127.0.0.1", 10004);
        // 2.通过Socket管道得到一个字节输出流并包装成高级的缓冲字符输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        // 3.通过输出流发送数据到服务器端
        while (true) {
            Scanner sc = new Scanner(System.in);
            bw.write(sc.nextLine());
            bw.newLine();
            bw.flush();
        }
    }
}
// 客户端B
class ClientDemo3 {
    public static void main(String[] args) throws Exception {
        // 1.创建客户端Socket对象,输入服务器端的IP地址和端口号,请求连接服务器端
        Socket socket = new Socket("127.0.0.1", 10004);
        // 2.通过Socket管道得到一个字节输出流
        OutputStream os = socket.getOutputStream();
        // 3.通过字节输出流输出数据到服务器端
        PrintStream ps = new PrintStream(os);
        while (true) {
            Scanner sc  = new Scanner(System.in);
            ps.println(sc.nextLine());
            ps.flush();
        }
    }
}

在这里插入图片描述
案例2:

// 服务端
// 通过线程池解决BIO的阻塞问题
// 每创建一次连接就分配一个线程处理连接事件
public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(10001);
        ExecutorService pool = Executors.newFixedThreadPool(5);
        while (true) {
            Socket socket = serverSocket.accept();
            pool.submit(new ServerReader(socket));
        }
    }
}
// 线程
class ServerReader implements Runnable {
    private Socket socket;

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

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println("客户端" + socket.getRemoteSocketAddress() + "说:" + line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// 客户端A
class Client1 {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("127.0.0.1", 10001);
        // 2.通过Socket管道得到一个字节输出流
        OutputStream os = socket.getOutputStream();
        // 3.通过字节输出流输出数据到服务器端
        PrintStream ps = new PrintStream(os);
        while (true) {
            Scanner sc  = new Scanner(System.in);
            ps.println(sc.nextLine());
            ps.flush();
        }
    }
}
class Client2 {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("127.0.0.1", 10001);
        // 2.通过Socket管道得到一个字节输出流并包装成高级的缓冲字符输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        // 3.通过输出流发送数据到服务器端
        while (true) {
            Scanner sc = new Scanner(System.in);
            bw.write(sc.nextLine());
            bw.newLine();
            bw.flush();
        }
    }
}

小结:
1.多线程解决阻塞问题,具有程序效率高、但内存可能溢出的特点;
2.线程池解决阻塞问题,效率相对多线程而言比较低,但内存不会溢出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值