JAVA的BIO、NIO、AIO模式精解(一)

这篇具有很好参考价值的文章主要介绍了JAVA的BIO、NIO、AIO模式精解(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. BIO、NIO、AIO介绍

在不同系统或进程间数据交互,或高并发场景下都选哟网络通信。早期是基于性能低下的同步阻塞IO(BIO)实现。后支持非阻塞IO(NIO)。
前置须知:javsse,java多线程,javaIO,java网络模型
目的:局域网内通信,多系统间底层消息传递机制,高并发下大数据通信,游戏应用。

2 .java的io演进

2.1 IO模型基本说明

IO模型:性能取决于用什么通信模式或架构进行数据传输和接收。java共支持3种网络编程的IO,见标题

2.2 IO模型

javaBIO

同步并阻塞。服务器一个链接一个线程,即客户端请求服务器只启动一个线程处理,如果链接空闲则浪费线程开销。

javaNIO

同步非阻塞。服务器一个线程处理多个链接(请求)。即客户端请求都会注册到多路复用上。轮询到有IO请求就进行处理。
JAVA的BIO、NIO、AIO模式精解(一)

javaAIO(NIO2.0版本)

异步非阻塞。服务器一个有效请求一个线程,客户端IO请求都由OS先完成了在通知服务器创建线程处理,一般用于连接数较多且链接时间较长应用。

2.3 BIO、NIO、AIO使用场景

  1. BIO:连接小且固定架构。JDK1.4前唯一选择,简单。
  2. NIO:连接多且较短架构。比如聊天,弹幕,服务间通讯等,JDK1.4后支持,复杂。
  3. AIO:连接多且较长家都。比如相册服务,充分调用OS参与并发,JDK1.7后支持,复杂。

3. Java BIO

3.1 BIO介绍

相关类接口间java.io。一个链接创建一个线程。可通过线程池优化成多客户端链接。

3.2 BIO机制

JAVA的BIO、NIO、AIO模式精解(一)

3.3 传统的BIO编程实例

网络编程CS架构实现两个进程间通信,服务端提供IP+PORT,客户端通过链接操作向服务端监听的端口地址发起请求。基于TCP三次握手建立链接,通过套接字Socket进行通信。
同步阻塞种服务端serverSocket负责绑定IP地址,启动监听端口。客户端Socket负责发起请求。通过输入输出流进行同步阻塞通信。
特点:C/S完全同步,耦合。

public class Client {
    public static void main(String[] args) throws IOException {
        //1.创建socket对象请求服务端的链接
        Socket socket = new Socket("127.0.0.1", 9999);
        //2.从socket对象中获取一个字节输出流
        OutputStream os = socket.getOutputStream();
        //3.把字节输出流包装成一个打印流
        PrintStream ps = new PrintStream(os);
        ps.println("hello world!服务端");
        ps.flush();
    }
}
/**
 * 目标:客户端发送消息,服务端接受消息
 */
public class Server {
    public static void main(String[] args) {
        try {
            System.out.println("服务端启动");
            //1.定义ServerSocket对象惊醒服务器端口注册
            ServerSocket serverSocket = new ServerSocket(9999);
            //2.监听客户端的Socket链接请求
            Socket socket = serverSocket.accept();
            //3.从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //4.把字节输入流包装成一个缓冲字符输入流 要以行为单位读取
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            if ((msg = br.readLine()) != null) {
                System.out.println("服务端接收到:" + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

小结:

  • 在以上通信中,服务端会一直等待客户端的消息,如果客户端没有进行消息的发送,服务端将一直进入阻塞状态
  • 同时服务端是按照行 获取信息的,客户端也必须按照行 发送。否则服务端进入等待消息的阻塞态。

3.4 BIO实现多发和多收消息

在3中,只能客户端发送消息,服务端接收消息,并不能反复的接收和发送消息。改进:

Client:
        PrintStream ps = new PrintStream(os);
        Scanner sc = new Scanner(System.in);
        while(true) {
            System.out.println("input:");
            String msg = sc.nextLine();
            ps.println(msg);
            ps.flush();
        }
Server:
       while ((msg = br.readLine()) != null) {
           System.out.println("服务端接收到:" + msg);
       }

小结:

  • 服务端只能处理一个客户端请求,因为单线程,一次只能与一个客户端进行消息通信。

3.5 BIO下接收多个客户端

需在服务端引入多线程解决多客户端请求。这样就实现了一个客户端一个线程模型。

/**
 * 目标:实现服务端同时处理多个客户端的socket连接
 * 实现:服务端每接收一个客户端socket请求对象后都交给一个独立线程处理客户端的数据交互。
 */
public class Server {
    public static void main(String[] args) {
        try{
            //1.注册端口
            ServerSocket ss = new ServerSocket(9999);
            //2.定义死循环,不断接收客户端的Socket链接
            while(true) {
                Socket socket = ss.accept();
                //3.创建一独立的线程来处理与这个客户端的socket通信
                new ThreadServerReader(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadServerReader extends Thread {
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //从socket对象中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //使用缓冲字符输入流包装字节输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null) {
                System.out.println(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

小结:

  1. 每个Socket接收都会创建线程,线程竞争,切换上下文会影响性能。
  2. 每个线程都会占用栈空间和CPU资源
  3. 并不是每个Socket都进行IO操作,无意义的线程处理
  4. 客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或僵死。

3.6 伪异步IO编程

采用线程池和任务队列实现,当客户端接入,将Socket封装成一个Task交由后端的线程池进行处理。JDK线程池维护一个消息队列和N个活跃线程。对消息队列中socket任务进行处理,由于线程池可设置消息队列的大小和最大线程数,因此,资源可控,无论多少客户端并发访问,都不会资源不够。

/**
 * 目标:伪异步通信架构
 */
public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            //初始化线程池对象
            HandlerSocketServerPool pool = new HandlerSocketServerPool(6, 10);
            while(true) {
                Socket socket = ss.accept();
                //把socket封装成任务对象交给一个线程池来处理
                Runnable target = new ServerRunnableTarget(socket);
                pool.execute(target);
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
//线程池
public class HandlerSocketServerPool {
    //1.创建一个线程池
    private ExecutorService excutorService;

    //2.初始化
    public HandlerSocketServerPool(int maxThreadNum, int queueSize) {
        excutorService = new ThreadPoolExecutor(3, maxThreadNum, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
    }

    //3.提供方法提交任务给线程池的任务来暂存,等待线程池来执行
    public void execute(Runnable target){
        excutorService.execute(target);
    }
}
//功能
public class ServerRunnableTarget implements Runnable {
    private Socket socket;

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

    @Override
    public void run() {
        //处理客户端socket请求
        try {
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            if ((msg = br.readLine()) != null) {
                System.out.println(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

小结:

  • 伪异步io采用线程池实现,因此避免了为每个请求都创建独立线程造成资源耗尽的问题,由于底层还是同步阻塞,没解决根本问题。
  • 如果单个消息处理缓慢,或服务器全部阻塞。那么后面的socket的io消息都将在队列中排队,新的socket将被拒绝,客户端会大量链接超时。

3.7 基于BIO形势下的文件上传

/**
 * 实现客户端任意类型文件给服务端保存
 */
public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 8888);
            //把字节输出流包装成一个数据输出流
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            //发送文件后缀给服务端
            dos.writeUTF(".png");
            //把文件数据发送给服务端
            InputStream is = new FileInputStream("//Path");
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) > 0) {
                dos.write(buffer, 0, len);
            }
            dos.flush();
            //通知服务端接收完毕
            socket.shutdownOutput();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * 接收客户端任意类型文件并保存
 */
public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket socket = new ServerSocket(8888);
            while (true) {
                Socket accept = socket.accept();
                //交给独立线程来处理与这个客户端的文件通信需求
                new ServerReaderThread(accept).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ServerReaderThread extends Thread {
    private Socket socket;

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

    @Override
    public void run() {
        try {
            //得到数据输入流读取客户端发送过来的数据
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            //读取客户端发送的文件类型
            String suffix = dis.readUTF();
            System.out.println("收到文件,类型:" + suffix);
            //定义字节输出管道,负责把客户端发过来的数据写出
            FileOutputStream os = new FileOutputStream("C:\\path\\" + UUID.randomUUID().toString() + suffix);
            //从数据输入流中读取文件数据,写出到字节输出流中
            byte[] buffer = new byte[1024];
            int len;
            while ((len = dis.read(buffer)) > 0) {
                os.write(buffer, 0, len);
            }
            os.close();
            System.out.println("保存文件成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.8 javaBIO下的端口转发

需求:一个客户端消息可发送所有的客户端接收(群聊)
JAVA的BIO、NIO、AIO模式精解(一)

/**
 * BIO下服务端端口转发
 * 服务端需求:
 *      1.注册端口
 *      2.收到客户端的socket链接,交给独立的线程来处理
 *      3.把当前连接的客户端socket存入到一个所谓的在线socket集合中保存
 *      4.接收客户端消息,然后推送给当前所有在线的socket接收
 */
public class Server {
    //定义静态集合
    public static List<Socket> allSocketOnLine = new ArrayList<>();

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            while(true){
                Socket socket = ss.accept();
                //把登陆的客户端socket存入到一个在线集合中去
                allSocketOnLine.add(socket);
                //为当前登录成功的socket分配一个独立的线程来处理与之通信
                new ServerReaderThread(socket).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
public class ServerReaderThread extends Thread {
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run(){
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            while ((msg = br.readLine())!=null){
                //服务端接收到客户端的消息推送给当前所有在线socket
                sendMsgToAllClient(msg);
            }
        }catch (Exception e){
            System.out.println("当前有人下线!");
            //从在线socket中移除本socket
            Server.allSocketOnLine.remove(socket);
            e.printStackTrace();
        }
    }

    //把当前客户端发来的消息推送全部在线socket
    private void sendMsgToAllClient(String msg) throws IOException {
        for (Socket sk : Server.allSocketOnLine) {
            PrintStream ps = new PrintStream(sk.getOutputStream());
            ps.println(msg);
            ps.flush();
        }
    }
}

3.9 基于BIO下即时通信

项目功能

需要解决客户端到客户端的通信,即实现客户端间的端口消息转发。
功能说明:
1.客户端登陆:输入用户名和服务端ip
2.在线人数实时更新:用户登录同步更新客户端联系人列表
3.离线人数更新:下线同步
4.群聊:任一客户端消息转发所有客户端接收
5.私聊:选择某一对象发送消息
6.@消息:可@该用户,所有人可见
7.消息用户和时间点:服务端记录用户消息时间点,然后进行多路转发或选择。

服务端设计

服务端接收多个客户端

服务端需要接收多个客户端的接入。

  • 1.服务端需要接收多个客户端,目前我们采取的策略是一个客户端对应一个服务端线程。
  • 2.服务端除了要注册端口以外,还需要为每个客户端分配一个独立线程处理与之通信。

服务端主体代码,主要进行端口注册,和接收客户端,分配线程处理该客户端请求

服务端接收登录消息及检测离线

接收客户端的登陆消息。

实现步骤
  • 需要在服务端处理客户端的线程的登陆消息。
  • 需要注意的是,服务端需要接收客户端的消息可能有很多种。
    • 分别是登陆消息,群聊消息,私聊消息 和@消息。
    • 这里需要约定如果客户端发送消息之前需要先发送消息的类型,类型我们使用信号值标志(1,2,3)。
      • 1代表接收的是登陆消息
      • 2代表群发| @消息
      • 3代表了私聊消息
  • 服务端的线程中有异常校验机制,一旦发现客户端下线会在异常机制中处理,然后移除当前客户端用户,把最新的用户列表发回给全部客户端进行在线人数更新。
服务端接收群聊

接收客户端发来的群聊消息推送给当前在线的所有客户端

实现步骤
  • 接下来要接收客户端发来的群聊消息。
  • 需要注意的是,服务端需要接收客户端的消息可能有很多种。
    • 分别是登陆消息,群聊消息,私聊消息 和@消息。
    • 这里需要约定如果客户端发送消息之前需要先发送消息的类型,类型我们使用信号值标志(1,2,3)。
      • 1代表接收的是登陆消息
      • 2代表群发| @消息
      • 3代表了私聊消息
服务端接收私聊

私聊消息的推送逻辑.文章来源地址https://www.toymoban.com/news/detail-426637.html

实现步骤
  • 解决私聊消息的推送逻辑,私聊消息需要知道推送给某个具体的客户端
  • 我们可以接收到客户端发来的私聊用户名称,根据用户名称定位该用户的Socket管道,然后单独推送消息给该Socket管道。
  • 需要注意的是,服务端需要接收客户端的消息可能有很多种。
    • 分别是登陆消息,群聊消息,私聊消息 和@消息。
    • 这里需要约定如果客户端发送消息之前需要先发送消息的类型,类型我们使用信号值标志(1,2,3)。
      • 1代表接收的是登陆消息
      • 2代表群发| @消息
      • 3代表了私聊消息
    小结
demo地址:https://gitee.com/xuyu294636185/JAVA_IO_DEMO.git
  • 本节我们解决了私聊消息的推送逻辑,私聊消息需要知道推送给某个具体的客户端Socket管道
  • 我们可以接收到客户端发来的私聊用户名称,根据用户名称定位该用户的Socket管道,然后单独推送消息给该Socket管道。

客户端设计

到了这里,关于JAVA的BIO、NIO、AIO模式精解(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JAVA中三种I/O框架——BIO、NIO、AIO

    BIO,同步阻塞IO模型,应用程序发起系统调用后会一直等待数据的请求,直至内核从磁盘获取到数据并拷贝到用户空间; 在一般的场景中,多线程模型下的BIO是成本较低、收益较高的方式。但是,如果在高并发的场景下,过多的创建线程,会严重占据系统资源,降低系统对外

    2024年02月08日
    浏览(55)
  • Java中的三种I/O模型:BIO、NIO和AIO

    I/O(输入/输出)操作是任何应用程序中必不可少的一部分,它涉及到与文件、网络或其他设备之间的数据传输。Java提供了几种不同的I/O模型,其中最常见的是AIO(异步非阻塞I/O)、BIO(阻塞I/O)和NIO(非阻塞I/O)。这些模型在处理I/O操作时具有不同的工作方式、特性和适用

    2024年02月08日
    浏览(38)
  • BIO、NIO和AIO

    目录 一.引言 何为IO IO的过程 Java的3种网络IO模型 阻塞和非阻塞IO IO多路复用 异步和同步IO 二.BIO 三.NIO 1. 三大组件 Channel Buffer Selector 2.ByteBuffer 2.1ByteBuffer的使用 2.2ByteBuffer 结构 ​2.3ByteBuffer的常用方法 分配空间   向 buffer 写入数据 从 buffer 读取数据 字符串与 ByteBuffer 互转 分

    2024年02月12日
    浏览(38)
  • BIO、NIO、AIO 的区别

    Java面试题  阻塞IO。一个连接一个线程,当服务端接受到多个客户端的请求时,客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销 同步非阻塞IO 。一个线程处理多个连接。NIO 包含  Channel(通道)、Selector(选择

    2024年01月20日
    浏览(34)
  • BIO、NIO、AIO区别详解

    主线程发起io请求后,需要等待当前io操作完成,才能继续执行。 引入selector、channel、等概念,当主线程发起io请求后,轮询的查看系统是否准备好执行io操作,没有准备好则主线程不会阻塞会继续执行,准备好主线程会阻塞等待io操作完成。 主线程发起io请求后,不会阻塞,

    2024年02月07日
    浏览(44)
  • BIO、NIO、AIO 有什么区别?

    Java 中的I/O模型主要分为三类:BIO(Blocking I/O)、NIO(New I/O)和AIO(Asynchronous I/O)。它们在处理I/O操作时有着不同的工作方式和特点。 BIO是传统的I/O模型,也称为同步I/O。在BIO中,每个I/O操作都会阻塞线程,直到数据准备好或者操作完成。这意味着一个线程只能处理一个连

    2024年01月16日
    浏览(46)
  • BIO、NIO、AIO 有什么区别

    在Java中,BIO(Blocking I/O)、NIO(Non-blocking I/O)和AIO(Asynchronous I/O)都是用于处理I/O(输入/输出)操作的不同方式。它们在处理I/O时具有不同的特点和适用场景。 BIO(Blocking I/O): 阻塞式I/O模型,是Java最传统的I/O模型。 在BIO中,每个I/O操作都会阻塞当前线程,直到操作完

    2024年02月13日
    浏览(35)
  • BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程

    上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题

    2024年02月14日
    浏览(48)
  • 代码分析Java中的BIO与NIO

    OS:Win10(需要开启telnet服务,或使用第三方远程工具) Java版本:8 BIO(Block IO),即同步阻塞IO,特点为当客户端发起请求后,在服务端未处理完该请求之前,客户端将一直等待服务端的响应。而服务端在此时也专注于该请求的处理,无法处理其它客户端的请求。 在IDEA运行上述

    2024年02月14日
    浏览(38)
  • Java网络编程-深入理解BIO、NIO

    BIO BIO 为 Blocked-IO(阻塞 IO),在 JDK1.4 之前建立网络连接时,只能使用 BIO 使用 BIO 时,服务端会对客户端的每个请求都建立一个线程进行处理,客户端向服务端发送请求后,先咨询服务端是否有线程响应,如果没有就会等待或者被拒绝 BIO 基本使用代码: 服务端: 客户端:

    2024年02月04日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包