IO

IO

1、IO

IO模型:就是用什么样的通道或者说是通信模式和架构进行数据的传输和接收,很大程度上决定了程序通信的性能,Java共支持3种网络编程的I/O模型:BIO、NIO、AIO

根据不同的业务场景与性能需求选择不同的I/O模型

1.1、 IO模型

Java BIO

同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即服务端有请求连接时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销

Java NIO

Java NIO:同步非

阻塞,服务器是吸纳模式为一个线程处理多个请求(连接),即客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理

Java AIO

Java AIO(NIO.2):异步 异步非阻塞,服务器实现模型为一个有效请求一个线程,客户端的I/O请求都是有OS先完成了在通知服务器应用去启动线程进行处理,一般使用与连接数较多且连接时间较长的应用

1.2、BIO、NIO、AIO 适用场景分析

1、BIO方式适用于链接数目较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。

2、NIO方式适用于连接数目较多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等。编程比较复杂,JDK1.4开始支持。

3、AIO方式使用于连接数目较多且连接时间较长(重操作)的架构,比如相册服务器,重分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

2、Java BIO深入剖析

2.1、Java BIO基本介绍

BIO(blocking I/O):同步阻塞,服务器实现模式为一个连接一个线程,即客户端有链接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的开销,可以连接线程池机制改善(实现多个客户端连接服务器).

2.2、Java BIO工作机制

服务端

package com.zhao.BIO;
 ​
 import java.io.*;
 import java.net.ServerSocket;
 import java.net.Socket;
 ​
 /*
     目标:客户端发送消息,服务端接收消息
  */
 public class Server {
     public static void main(String[] args) {
 ​
         try {
             System.out.println("服务端启动!");
             //1.定义一个ServerSocket对象进行服务端的端口注册
             ServerSocket  ss = new ServerSocket(9999);
             //2.监听客户端的Socket连接请求
             Socket socket = ss.accept();
             //3.从socket普通中得到一个字节输入流对象
             InputStream is = socket.getInputStream();
             //4.把字节输入流包装成一个缓冲字符输入流
             //BufferedInputStream bis = new BufferedInputStream(is);   //4.把字节输入流包装成一个缓冲字节输入流,因为缓冲字节输入流不能按照行来读取,所以使用缓冲字符输入流
             BufferedReader br = new BufferedReader(new InputStreamReader(is));
             String msg ;
             if ((msg = br.readLine())!=null){//这里使用if可以做到一边发一边收,如果使用while可能会造成没有下一条数据连接破坏
                 System.out.println("服务端接收到:"+msg);
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }
 ​

客户端

package com.zhao.BIO;
 ​
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintStream;
 import java.net.Socket;
 ​
 //客户端
 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 word!服务端,你好!");//这里使用print可能会造成系统判断你发的不是一行数据而程序不会执行!
         ps.flush();
     }
 }
 ​

小结

  • 在以上通信中,服务端会一直等待客户端的消息,如果客户端没有进行消息的发送,服务端将会一直进入阻塞状态。

  • 同时服务端是按照行获取消息的,这意味着客户端也必须按照行进行消息的发送,否则服务端将进入等待消息的阻塞状态!

2.3、BIO模式下多发和多收消息

只能实现客户端发送消息,服务端接收消息,并不能实现反复的收消息和反复的发消息,我们只需要在客户端案例中,加上反复执行发送消息的逻辑即可!

客户端:

package com.zhao.BIO.Demo02;
 ​
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintStream;
 import java.net.Socket;
 import java.util.Scanner;
 ​
 //客户端
 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);
         Scanner scan = new Scanner(System.in);
         while (true){
             System.out.print("请讲:");
             String msg = scan.nextLine();
             ps.println(msg);
             ps.flush();
         }
     }
 }

服务端:

package com.zhao.BIO.Demo02;
 ​
 import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.ServerSocket;
 import java.net.Socket;
 ​
 /*
     目标:服务端可以反复的接收消息,客户端可以反复的发送消息
  */
 public class Server {
     public static void main(String[] args) {
 ​
         try {
             System.out.println("服务端启动!");
             //1.定义一个ServerSocket对象进行服务端的端口注册
             ServerSocket  ss = new ServerSocket(9999);
             //2.监听客户端的Socket连接请求
             Socket socket = ss.accept();
             //3.从socket普通中得到一个字节输入流对象
             InputStream is = socket.getInputStream();
             //BufferedInputStream bis = new BufferedInputStream(is);   //4.把字节输入流包装成一个缓冲字节输入流,所以采用缓冲字符输入流
             //4.把字节输入流包装成一个缓冲字符输入流
             BufferedReader br = new BufferedReader(new InputStreamReader(is));
             String msg ;
             while ((msg = br.readLine())!=null){//这里使用if可以做到一边发一边收,如果使用while可能会造成没有下一条数据连接破坏
                 System.out.println("服务端接收到:"+msg);
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }

2.4、BIO模式下接收多个客户端

概述

在上述案例中,一个服务端只能接收一个客户端的请求发送,那么如果服务端需要处理很多个客户端的消息请求应该如何处理呢,此时我们就需要在服务端引入线程了,也就是说客户端没发送一个请求,服务端就创建一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型。

服务端

package com.zhao.BIO.Demo03;
 ​
 import java.io.IOException;
 import java.net.ServerSocket;
 import java.net.Socket;
 ​
 /*
     目标:实现服务端可以同时接收多个客户端的Socket通信请求。
     思路:是服务端没接收到一个客户端socket请求对象之后都交给一个独立的线程
  */
 public class Server {
     public static void main(String[] args) {
         try {
 ​
             //1.注册端口
             ServerSocket ss = new ServerSocket(8888);
             //2.定义一个死循环,负责不断的接收客户端的socket连接请求
             while (true){
                 Socket socket = ss.accept();
                 //3.创建一个独立的线程来处理与这个客户端的socket通信
                 new ServerThreadReader(socket).start();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 ​
 }
 ​
package com.zhao.BIO.Demo03;
 ​
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.Socket;
 ​
 public class ServerThreadReader extends Thread{
 ​
     private Socket socket;
 ​
     public ServerThreadReader(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 (IOException e) {
             e.printStackTrace();
         }
     }
 }
 ​

客户端

package com.zhao.BIO.Demo03;
 ​
 import java.io.IOException;
 import java.io.PrintStream;
 import java.net.Socket;
 import java.util.Scanner;
 ​
 /*
     客户端
  */
 public class Client {
     public static void main(String[] args) {
         try {
             //1.请求服务端的socket对象连接
             Socket socket = new Socket("127.0.0.1",8888);
             //2.得到一个打印流
             PrintStream ps = new PrintStream(socket.getOutputStream());
             //3.使用循环不断的发送消息给服务端接收
             Scanner scan = new Scanner(System.in);
             while (true){
                 System.out.println("请说:");
                 String msg = scan.nextLine();
                 ps.println(msg);
                 ps.flush();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 }
 ​

总结:

  1. 每个socket接收到,都会创建一个线程,线程的竞技、切换上下文影响性能;

  2. 每个线程都会占用桟空间和CPU资源;

  3. 并不是每个socket都进行IO操作,无意义的线程处理;

  4. 客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统发生线程桟溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。

2.5、伪异步I/O编程

概述

在上述案例中:客户端的并发访问增加时。服务端将呈现1:1的线程开销,,访问量越大,系统将发生线程栈溢出,创建线程失败,最终导致线程宕机或者僵死,从而不能对外提供服务。

接下来我们采用一个伪同步I/O得物通信框架,采用线程池和任务队列实现,当客户端接入时,将客户端的socket封装成一个Task(该任务实现java.lang.Runnable线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息对列和N个活跃的线程,对消息队列中socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此,他的资源占用是可控的,无论多少个客户端并发访问,都不会造成资源的耗尽和宕机。

图示:

代码如下:

服务端

package com.zhao.BIO.Demo04;
 ​
 import java.io.IOException;
 import java.net.ServerSocket;
 import java.net.Socket;
 ​
 /*
     目标:开发实现伪异步通信架构
  */
 public class Server {
     public static void main(String[] args) {
         try {
             System.out.println("服务器端启动!");
             //1.注册端口
             ServerSocket ss = new ServerSocket(8888);
             //2.定义一个循环用于接收客户端的socket请求
 ​
             //初始化一个线程池对象
             HandlerSocketServerPool pool = new HandlerSocketServerPool(6,10);
 ​
             while (true){
 ​
                 Socket socket = ss.accept();
                 //3.把socket对象交给一个线程池进行处理
                 //把socket封装成一个任务对象交给线程池处理
                 Runnable target = new ServerRunnableTarget(socket);
                 pool.execute(target);
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 }
package com.zhao.BIO.Demo04;
 ​
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 ​
 public class HandlerSocketServerPool {
     //1.创建一个线程池的成员变量用于存储一个线程池对象
     private ExecutorService executorService;
 ​
     /*
         2.创建这个类的对象的时候就需要初始化线程池对象
             public ThreadPoolExecutor(int corePoolSize,
                               int maximumPoolSize,
                               long keepAliveTime,
                               TimeUnit unit,
                               BlockingQueue<Runnable> workQueue) {
         this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
              Executors.defaultThreadFactory(), defaultHandler);
     }
 ​
      */
     public HandlerSocketServerPool(int maxThreadNum ,int queueSize){
         executorService = new ThreadPoolExecutor(5,maxThreadNum,120
                 , TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize));
     }
 ​
     //3.提供一个方法来提交任务给线程池的任务队列来暂存,等着线程池来处理
     public void execute(Runnable target){
         executorService.execute(target);
     }
 }
package com.zhao.BIO.Demo04;

import com.zhao.BIO.Demo03.ServerThreadReader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerRunnableTarget implements Runnable{
   private Socket socket;

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

    @Override
    public void run() {
        try {
            //1.从socket普通中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //2.把字节输入流包装成一个缓冲字符输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg ;
            while ((msg = br.readLine())!=null){
                System.out.println("服务端接收到:"+msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端

package com.zhao.BIO.Demo04;

import java.io.IOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket(InetAddress.getLocalHost(),8888);
            PrintStream ps = new PrintStream(socket.getOutputStream());
            Scanner scan = new Scanner(System.in);
            while (true){
                System.out.println("请说");
                String msg = scan.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

小结:

  • 伪异步io才用了线程池实现,因此避免了为每个请求创建一个独立线程造成线程资源耗尽的问题,但由于底层依然是采用的同步阻塞模型,因此无法从根本上解决问题。

  • 如果单个消息处理的缓慢,或者服务器线程池中的全部线程都被阻塞,那么后续socket的io消息都将在队列中排队。新的socket请求将会被拒绝,客户端会发生大量连接超时。

2.5、基于BIO形势下的文件上传

目标:支持任意类型的文件上传

客户端

package com.zhao.BIO.file;

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;

/*
    目标:实现客户端上传任意类型的文件数据给服务端保存起来。
 */
public class Client {
    public static void main(String[] args) {

        try {
            //1.请求与socket连接
            Socket socket = new Socket("127.0.0.1",8888);
            //2.把字节输出流包装成一个数据输出流
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            //3.先发送上传文件的后缀给服务端
            dos.writeUTF(".md");
            //4.把文件数据发送给服务端进行接收
            FileInputStream is = new FileInputStream("/Users/mac/Desktop/IO的.md");

            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) >0){
                dos.write(buffer,0,len);
            }
            dos.flush();
            socket.shutdownOutput();//通知服务端这边的数据发送完毕了

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

    }
}

服务端

package com.zhao.BIO.file;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        try {
            //1.连接
            ServerSocket ss = new ServerSocket(8888);
            while (true){
                Socket socket = ss.accept();
                //交给一个独立的线程来处理与这个客户端的文件通信需求
                new ServerReaderThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.zhao.BIO.file;

import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.UUID;

public class ServerReaderThread  extends Thread{

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

    @Override
    public void run() {
        OutputStream os = null;

        try {
            //1.得到一个数据输入流读取客户端发过来的数据
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            //2.读取客户端发过来的文件类型
            String suffix = dis.readUTF();
            System.out.println("服务端已经成功接收到了文件类型:"+suffix);
            //3.定义一个字节输出管道负责把客户端发来的文件数据写出去
            os = new FileOutputStream("/Users/mac/Desktop/server\\ " +
                    UUID.randomUUID().toString() + suffix);
            //4.从数据输入流中读取文件数据,写出到字节输入流中去
            byte[] buffer = new byte[1024];
            int len;
            while ((len = dis.read(buffer)) >0){
                os.write(buffer,0,len);
            }
            os.close();
            System.out.println("服务端接收文件保存成功!");

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

2.6、BIO模式下的端口转发思想

需求:需要实现一个客户端的消息可以发给所有的客户端去接收。(群聊实现)

服务端

package com.zhao.BIO.chat;
 ​
         /*
         目标:BIO模式下的端口转发思想~服务端实现
 ​
         服务端实现的需求:
             1.注册端口
             2.接收客户端的socket连接,交给一个独立的线程来处理
             3.把当前连接的客户端socket存入到一个所谓的在线socket集合中保存
             4.接收客户端的消息,然后推送给当前所有在线的socket接收。
 ​
          */
 ​
 import java.io.IOException;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.util.ArrayList;
 import java.util.List;
 ​
 public class Server {
     //定义一个静态集合
     public static List<Socket> allSocketOnLine = new ArrayList<>();
     public static void main(String[] args) {
         try {
             ServerSocket ss = new ServerSocket(8888);
             while (true){
                 Socket socket = ss.accept();
                 //把登录的客户端socket存入到一个在线集合中
                 allSocketOnLine.add(socket);
                 //为当前登录成功的socket分配一个独立的线程来处理与之通信
                 new ServerReaderThread(socket).start();
             }
 ​
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 }
package com.zhao.BIO.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class ServerReaderThread extends Thread{

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

    }
    @Override
    public void run() {
        try {
            //1.从socket中去获取当前客户端的输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            while ((msg = br.readLine())!=null){
                //2.服务端接收到了客户端的消息之后,是需要推送给当前所有在线的socket
                sendMsgToAllClient(msg);
            }


        } catch (IOException e) {
            System.out.println("当前有人下线了!");
            //从在线的socket集合中移除本socket
            Server.allSocketOnLine.remove(socket);
        }
    }

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

2.6、基于BIO 模式下的即时通信

基于BIO模式下的即时通信,我们需要解决客户端到客户端的通信,也就是需要实现客户端与客户端的端口消息转发逻辑。

项目功能演示

项目案例说明

本项目案例为即时通信的软件项目,适合基础加强的大案例,具备综合性。学习本项目案例至少需要具备如下java SE技术点:

  1. Java面向对象设计,语法设计。

  2. 多线程技术。

  3. IO流技术。

  4. 网络通信相关技术。

  5. 集合框架。

  6. 项目开发思维。

  7. Java常用api使用。

…..

功能清单简单说明:

1.客户端登录功能

  • 可以实现启动客户端进行登录,客户端登录只需要输入用户名和服务器ip地址即可。

2.在线人数即时更新

  • 客户端用户登录以后,需要同步更新所有客户端的联系人信息栏。

3.离线人数更新

  • 检测到有客户端下线后,需要同步更新所有客户端的联系人信息栏

4.群聊

  • 任意一个客户端的消息,可以推送给当前所有客户端接收。

5.私聊

  • 可以选择某个员工,点击私聊按钮,然后发出的消息可以被该客户端单独接收。

6.@消息

  • 可以选择某个员工,然后发出的消息可以@该用户,但是其他所有人都能

7.消息用户和消息时间点

  • 服务端可以实时登录该用户的消息时间点,然后进行消息的多路转发或者选择。

服务端接收群消息

目标

实现了接收客户端的登录消息,然后提取当前在线的全部的用户名和当前登录的用户名称发送给全部在线用户更新自己的在线人数列表。接下来就接收客户端发来的群聊消息推送给当前在线的所有客户端。

实现步骤

  • 接下来就接收客户端发来的群聊消息

  • 需要注意的是,服务端需要接收客户端的消息可能有很多种

    • 这里是登录消息,群聊消息,私发消息和@消息

    • 这里需要约定如果客户端发送消息之前需要先发送消息的类型,类型我们使用(1,2,3)

      • 这里1代表的是登录消息

      • 2代表了|@消息

      • 3代表了私发消息

服务端设计
package com.zhao.BIO.instant_messageing.server;
 ​
 import com.zhao.BIO.instant_messageing.utils.Constants;
 ​
 import java.io.IOException;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.util.HashMap;
 import java.util.Map;
 ​
 public class ServerChat {
 ​
     /** 定义一个集合存放所有在线的socket  */
     public static Map<Socket, String> onLineSockets = new HashMap<>();
 ​
     public static void main(String[] args) {
         try {
             /** 1.注册端口   */
             ServerSocket serverSocket = new ServerSocket(Constants.PORT);
 ​
             /** 2.循环一直等待所有可能的客户端连接 */
             while(true){
                 Socket socket = serverSocket.accept();
                 /**3. 把客户端的socket管道单独配置一个线程来处理 */
                 new ServerReader(socket).start();
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }
 ​
 ​

线程实现类

package com.zhao.BIO.instant_messageing.server;
 ​
 import com.zhao.BIO.instant_messageing.utils.Constants;
 ​
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.net.Socket;
 import java.text.SimpleDateFormat;
 import java.util.Collection;
 import java.util.Set;
 ​
 public class ServerReader extends Thread {
     private Socket socket;
     public ServerReader(Socket socket) {
         this.socket = socket;
     }
 ​
     @Override
     public void run() {
         DataInputStream dis = null;
         try {
             dis = new DataInputStream(socket.getInputStream());
             /** 1.循环一直等待客户端的消息 */
             while(true){
                 /** 2.读取当前的消息类型 :登录,群发,私聊 , @消息 */
                 int flag = dis.readInt();
                 if(flag == 1){
                     /** 先将当前登录的客户端socket存到在线人数的socket集合中   */
                     String name = dis.readUTF() ;
                     System.out.println(name+"---->"+socket.getRemoteSocketAddress());
                     ServerChat.onLineSockets.put(socket, name);
                 }
                 writeMsg(flag,dis);
             }
         } catch (Exception e) {
             System.out.println("--有人下线了--");
             // 从在线人数中将当前socket移出去
             ServerChat.onLineSockets.remove(socket);
             try {
                 // 从新更新在线人数并发给所有客户端
                 writeMsg(1,dis);
             } catch (Exception e1) {
                 e1.printStackTrace();
             }
         }
 ​
     }
 ​
     private void writeMsg(int flag, DataInputStream dis) throws Exception {
         // DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
         // 定义一个变量存放最终的消息形式
         String msg = null ;
         if(flag == 1){
             /** 读取所有在线人数发给所有客户端去更新自己的在线人数列表 */
             /** onlineNames = [波仔,zhangsan,波妞]*/
             StringBuilder rs = new StringBuilder();
             Collection<String> onlineNames = ServerChat.onLineSockets.values();
             // 判断是否存在在线人数
             if(onlineNames != null && onlineNames.size() > 0){
                 for(String name : onlineNames){
                     rs.append(name+ Constants.SPILIT);
                 }
                 // 波仔003197♣♣㏘♣④④♣zhangsan003197♣♣㏘♣④④♣波妞003197♣♣㏘♣④④♣
                 // 去掉最后的一个分隔符
                 msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT));
 ​
                 /** 将消息发送给所有的客户端 */
                 sendMsgToAll(flag,msg);
             }
         }else if(flag == 2 || flag == 3){
             // 读到消息  群发的 或者 @消息
             String newMsg = dis.readUTF() ; // 消息
             // 得到发件人
             String sendName = ServerChat.onLineSockets.get(socket);
 ​
             // 内容
             StringBuilder msgFinal = new StringBuilder();
             // 时间
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE");
             if(flag == 2){
                 msgFinal.append(sendName).append("  ").append(sdf.format(System.currentTimeMillis())).append("\r\n");
                 msgFinal.append("    ").append(newMsg).append("\r\n");
                 sendMsgToAll(flag,msgFinal.toString());
             }else if(flag == 3){
                 msgFinal.append(sendName).append("  ").append(sdf.format(System.currentTimeMillis())).append("对您私发\r\n");
                 msgFinal.append("    ").append(newMsg).append("\r\n");
                 // 私发
                 // 得到给谁私发
                 String destName = dis.readUTF();
                 sendMsgToOne(destName,msgFinal.toString());
             }
         }
     }
     /**
      * @param destName 对谁私发
      * @param msg 发的消息内容
      * @throws Exception
      */
     private void sendMsgToOne(String destName, String msg) throws Exception {
         // 拿到所有的在线socket管道 给这些管道写出消息
         Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
         for(Socket sk :  allOnLineSockets){
             // 得到当前需要私发的socket
             // 只对这个名字对应的socket私发消息
             if(ServerChat.onLineSockets.get(sk).trim().equals(destName)){
                 DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
                 dos.writeInt(2); // 消息类型
                 dos.writeUTF(msg);
                 dos.flush();
             }
         }
 ​
     }
 ​
 ​
     private void sendMsgToAll(int flag, String msg) throws Exception {
         // 拿到所有的在线socket管道 给这些管道写出消息
         Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
         for(Socket sk :  allOnLineSockets){
             DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
             dos.writeInt(flag); // 消息类型
             dos.writeUTF(msg);
             dos.flush();
         }
     }
 }
 ​
客户端设计

启动客户端界面,登录,刷新在线

目标

实现步骤

  • 客户端主要界面是GUI设计,主题页面分为登录页面和聊天窗口,以及在线用户列表

  • GUI界面可以自行复制使用

  • 登录输入服务端ip和用户名后,要求与服务端的登录,然后立即为当前用户端分配一个读线程处理客户端的读数据消息。因为客户端可能随时会接收到服务端那边转发过来的各种即时消息信息。

  • 客户端登录完成,服务端收到登录的用户名后,会立即发来最新的用户列表给客户端更新。

代码实现

客户端主题代码:

package com.zhao.BIO.instant_messageing.Client;
 ​
 import com.zhao.BIO.instant_messageing.utils.Constants;
 ​
 import javax.swing.*;
 import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.io.DataOutputStream;
 import java.net.Socket;
 ​
 public class ClientChat implements ActionListener {
     /** 1.设计界面  */
     private JFrame win = new JFrame();
     /** 2.消息内容框架 */
     public JTextArea smsContent =new JTextArea(23 , 50);
     /** 3.发送消息的框  */
     private JTextArea smsSend = new JTextArea(4,40);
     /** 4.在线人数的区域  */
     /** 存放人的数据 */
     /** 展示在线人数的窗口 */
     public JList<String> onLineUsers = new JList<>();
 ​
     // 是否私聊按钮
     private JCheckBox isPrivateBn = new JCheckBox("私聊");
     // 消息按钮
     private JButton sendBn  = new JButton("发送");
 ​
     // 登录界面
     private JFrame loginView;
 ​
     private JTextField ipEt , nameEt , idEt;
 ​
     private Socket socket ;
 ​
     public static void main(String[] args) {
         new ClientChat().initView();
 ​
     }
 ​
     private void initView() {
         /** 初始化聊天窗口的界面 */
         win.setSize(650, 600);
 ​
         /** 展示登录界面  */
         displayLoginView();
 ​
         /** 展示聊天界面 */
         //displayChatView();
 ​
     }
 ​
     private void displayChatView() {
 ​
         JPanel bottomPanel = new JPanel(new BorderLayout());
         //-----------------------------------------------
         // 将消息框和按钮 添加到窗口的底端
         win.add(bottomPanel, BorderLayout.SOUTH);
         bottomPanel.add(smsSend);
         JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
         btns.add(sendBn);
         btns.add(isPrivateBn);
         bottomPanel.add(btns, BorderLayout.EAST);
         //-----------------------------------------------
         // 给发送消息按钮绑定点击事件监听器
         // 将展示消息区centerPanel添加到窗口的中间
         smsContent.setBackground(new Color(0xdd,0xdd,0xdd));
         // 让展示消息区可以滚动。
         win.add(new JScrollPane(smsContent), BorderLayout.CENTER);
         smsContent.setEditable(false);
         //-----------------------------------------------
         // 用户列表和是否私聊放到窗口的最右边
         Box rightBox = new Box(BoxLayout.Y_AXIS);
         onLineUsers.setFixedCellWidth(120);
         onLineUsers.setVisibleRowCount(13);
         rightBox.add(new JScrollPane(onLineUsers));
         win.add(rightBox, BorderLayout.EAST);
         //-----------------------------------------------
         // 关闭窗口退出当前程序
         win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         win.pack();  // swing 加上这句 就可以拥有关闭窗口的功能
         /** 设置窗口居中,显示出来  */
         setWindowCenter(win,650,600,true);
         // 发送按钮绑定点击事件
         sendBn.addActionListener(this);
     }
 ​
     private void displayLoginView(){
 ​
         /** 先让用户进行登录
          *  服务端ip
          *  用户名
          *  id
          *  */
         /** 显示一个qq的登录框     */
         loginView = new JFrame("登录");
         loginView.setLayout(new GridLayout(3, 1));
         loginView.setSize(400, 230);
 ​
         JPanel ip = new JPanel();
         JLabel label = new JLabel("   IP:");
         ip.add(label);
         ipEt = new JTextField(20);
         ip.add(ipEt);
         loginView.add(ip);
 ​
         JPanel name = new JPanel();
         JLabel label1 = new JLabel("姓名:");
         name.add(label1);
         nameEt = new JTextField(20);
         name.add(nameEt);
         loginView.add(name);
 ​
         JPanel btnView = new JPanel();
         JButton login = new JButton("登陆");
         btnView.add(login);
         JButton cancle = new JButton("取消");
         btnView.add(cancle);
         loginView.add(btnView);
         // 关闭窗口退出当前程序
         loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         setWindowCenter(loginView,400,260,true);
 ​
         /** 给登录和取消绑定点击事件 */
         login.addActionListener(this);
         cancle.addActionListener(this);
 ​
     }
 ​
     private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) {
         /** 得到所在系统所在屏幕的宽高 */
         Dimension ds = frame.getToolkit().getScreenSize();
 ​
         /** 拿到电脑的宽 */
         int width1 = ds.width;
         /** 高 */
         int height1 = ds.height ;
 ​
         System.out.println(width1 +"*" + height1);
         /** 设置窗口的左上角坐标 */
         frame.setLocation(width1/2 - width/2, height1/2 -height/2);
         frame.setVisible(flag);
     }
 ​
     @Override
     public void actionPerformed(ActionEvent e) {
         /** 得到点击的事件源 */
         JButton btn = (JButton) e.getSource();
         switch(btn.getText()){
             case "登陆":
                 String ip = ipEt.getText().toString();
                 String name = nameEt.getText().toString();
                 // 校验参数是否为空
                 // 错误提示
                 String msg = "" ;
                 // 12.1.2.0
                 // \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\
                 if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
                     msg = "请输入合法的服务端ip地址";
                 }else if(name==null || !name.matches("\\S{1,}")){
                     msg = "姓名必须1个字符以上";
                 }
 ​
                 if(!msg.equals("")){
                     /** msg有内容说明参数有为空 */
                     // 参数一:弹出放到哪个窗口里面
                     JOptionPane.showMessageDialog(loginView, msg);
                 }else{
                     try {
                         // 参数都合法了
                         // 当前登录的用户,去服务端登陆
                         /** 先把当前用户的名称展示到界面 */
                         win.setTitle(name);
                         // 去服务端登陆连接一个socket管道
                         socket = new Socket(ip, Constants.PORT);
 ​
                         //为客户端的socket分配一个线程 专门负责收消息
                         new ClientReader(this,socket).start();
 ​
                         // 带上用户信息过去
                         DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                         dos.writeInt(1); // 登录消息
                         dos.writeUTF(name.trim());
                         dos.flush();
 ​
                         // 关系当前窗口 弹出聊天界面
                         loginView.dispose(); // 登录窗口销毁
                         displayChatView(); // 展示了聊天窗口了
 ​
 ​
                     } catch (Exception e1) {
                         e1.printStackTrace();
                     }
                 }
                 break;
             case "取消":
                 /** 退出系统 */
                 System.exit(0);
                 break;
             case "发送":
                 // 得到发送消息的内容
                 String msgSend = smsSend.getText().toString();
                 if(!msgSend.trim().equals("")){
                     /** 发消息给服务端 */
                     try {
                         // 判断是否对谁发消息
                         String selectName = onLineUsers.getSelectedValue();
                         int flag = 2 ;// 群发 @消息
                         if(selectName!=null&&!selectName.equals("")){
                             msgSend =("@"+selectName+","+msgSend);
                             /** 判断是否选中了私法 */
                             if(isPrivateBn.isSelected()){
                                 /** 私法 */
                                 flag = 3 ;//私发消息
                             }
 ​
                         }
 ​
                         DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                         dos.writeInt(flag); // 群发消息  发送给所有人
                         dos.writeUTF(msgSend);
                         if(flag == 3){
                             // 告诉服务端我对谁私发
                             dos.writeUTF(selectName.trim());
                         }
                         dos.flush();
 ​
                     } catch (Exception e1) {
                         e1.printStackTrace();
                     }
 ​
                 }
                 smsSend.setText(null);
                 break;
 ​
         }
 ​
     }
 }
 ​
 ​
package com.zhao.BIO.instant_messageing.Client;
 ​
 import com.zhao.BIO.instant_messageing.utils.Constants;
 ​
 import java.io.DataInputStream;
 import java.net.Socket;
 ​
 class ClientReader extends Thread {
 ​
     private Socket socket;
     private ClientChat clientChat ;
 ​
     public ClientReader(ClientChat clientChat, Socket socket) {
         this.clientChat = clientChat;
         this.socket = socket;
     }
 ​
     @Override
     public void run() {
         try {
             DataInputStream dis = new DataInputStream(socket.getInputStream());
             /** 循环一直等待客户端的消息 */
             while(true){
                 /** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
                 int flag = dis.readInt();
                 if(flag == 1){
                     // 在线人数消息回来了
                     String nameDatas = dis.readUTF();
                     // 展示到在线人数的界面
                     String[] names = nameDatas.split(Constants.SPILIT);
 ​
                     clientChat.onLineUsers.setListData(names);
                 }else if(flag == 2){
                     //群发,私聊 , @消息 都是直接显示的。
                     String msg = dis.readUTF() ;
                     clientChat.smsContent.append(msg);
                     // 让消息界面滾動到底端
                     clientChat.smsContent.setCaretPosition(clientChat.smsContent.getText().length());
                 }
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }
 ​
 ​

3、Java NIO深入剖析

3.1、Java NIO基本介绍

Java NIO(New IO)也有人称之为 java non-blocking IO是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。NIO可以理解为非阻塞IO,传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,而NIO中可以配置socket为非阻塞模式。

NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。

NIO 有三大核心部分:Channel( 通道)Buffer( 缓冲区), Selector( 选择器)

Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。通俗理解:NIO 是可以做到用一个线程来处理多个操作的。假设有 1000 个请求过来,根据实际情况,可以分配20 或者 80个线程来处理。不像之前的阻塞 IO 那样,非得分配 1000 个。

3.2、NIO 和 BIO 的比较

  • BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多

  • BIO 是阻塞的,NIO 则是非阻塞的

  • BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道

NIOBIO
面向缓冲区(Buffer)面向流(Stream)
非阻塞(Non Blocking IO)阻塞IO(Blocking IO)
选择器(Selectors) 

3.2、 NIO三大核心原理示意图

NIO 有三大核心部分:Channel( 通道)Buffer( 缓冲区), Selector( 选择器)

Buffer缓冲区

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。相比较直接对数组的操作,Buffer API更加容易操作和管理。

Channel(通道)

Java NIO的通道类似流,但又有些不同:既可以从通道中读取数据,又可以写数据到通道。但流的(input或output)读写通常是单向的。 通道可以非阻塞读取和写入通道,通道可以支持读取或写入缓冲区,也支持异步地读写。

Selector选择器

Selector是 一个Java NIO组件,可以能够检查一个或多个 NIO 通道,并确定哪些通道已经准备好进行读取或写入。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接,提高效率

  • 每个 channel 都会对应一个 Buffer

  • 一个线程对应Selector , 一个Selector对应多个 channel(连接)

  • 程序切换到哪个 channel 是由事件决定的

  • Selector 会根据不同的事件,在各个通道上切换

  • Buffer 就是一个内存块 , 底层是一个数组

  • 数据的读取写入是通过 Buffer完成的 , BIO 中要么是输入流,或者是输出流, 不能双向,但是 NIO 的 Buffer 是可以读也可以写。

  • Java NIO系统的核心在于:通道(Channel)和缓冲区 (Buffer)。通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用 NIO 系统,需要获取 用于连接 IO 设备的通道以及用于容纳数据的缓冲 区。然后操作缓冲区,对数据进行处理。简而言之,Channel 负责传输, Buffer 负责存取数据

3.3、NIO核心一:缓冲区(Buffer)

3.3.1、缓冲区(Buffer)

一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区 都是 Buffer 抽象类的子类.。Java NIO 中的 Buffer 主要用于与 NIO 通道进行 交互,数据是从通道

读入缓冲区,从缓冲区写入通道中的。

3.3.2、Buffer及其子类

Buffer就像一个数组,可以保存多个相同类型的数据。根据数据类型不同 ,有以下 Buffer 常用子类:

  • ByteBuffer

  • CharBuffer

  • ShortBuffer

  • IntBuffer

  • LongBuffer

  • FloatBuffer

  • DoubleBuffer

上述 Buffer 类他们都采用相似的方法进行管理数据,只是各自 管理的数据类型不同而已。都是通过如下方法获取一个 Buffer 对象:

static XxxBuffer allocate(int capacity) : 创建一个容量为capacity 的 XxxBuffer 对象
3.3.3缓冲区的基本属性

缓冲区的基本属性Buffer 中的重要概念:

容量 (capacity) :作为一个内存块,Buffer具有一定的固定大小,也称为"容量",缓冲区容量不能为负,并且创建后不能更改。 限制 (limit):表示缓冲区中可以操作数据的大小(limit 后数据不能进行读写)。缓冲区的限制不能为负,并且不能大于其容量。 写入模式,限制等于buffer的容量。读取模式下,limit等于写入的数据量。

位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为 负,并且不能大于其限制

标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法 指定 Buffer 中一个 特定的 position,之后可以通过调用 reset() 方法恢复到这 个 position.标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

3.3.4、Buffer常见方法
Buffer clear() 清空缓冲区并返回对缓冲区的引用
 Buffer flip() 为 将缓冲区的界限设置为当前位置,并将当前位置充值为 0
 int capacity() 返回 Buffer 的 capacity 大小
 boolean hasRemaining() 判断缓冲区中是否还有元素
 int limit() 返回 Buffer 的界限(limit) 的位置
 Buffer limit(int n) 将设置缓冲区界限为 n,并返回一个具有新 limit 的缓冲区对象
 Buffer mark() 对缓冲区设置标记
 int position() 返回缓冲区的当前位置 position
 Buffer position(int n) 将设置缓冲区的当前位置为 n,并返回修改后的 Buffer 对象
 int remaining() 返回 position 和 limit 之间的元素个数
 Buffer reset() 将位置 position 转到以前设置的mark 所在的位置
 Buffer rewind() 将位置设为为 0, 取消设置的 mark
3.3.5缓冲区的数据操作
Buffer 所有子类提供了两个用于数据操作的方法:get()put() 方法取获取 Buffer中的数据
 get() :读取单个字节
 get(byte[] dst):批量读取多个字节到 dst 中
 get(int index):读取指定索引位置的字节(不会移动 position)
   
 放到 入数据到 Buffer 中 中
 put(byte b):将给定单个字节写入缓冲区的当前位置
 put(byte[] src):将 src 中的字节写入缓冲区的当前位置
 put(int index, byte b):将指定字节写入缓冲区的索引位置(不会移动 position)

使用Buffer读写数据一般遵循以下四个步骤:

  • 1 写入数据到Buffer

  • 2 调用flip()方法,转换为读取模式

  • 3 从Buffer中读取数据

  • 4 调用buffer.clear()方法或者buffer.compact()方法清除缓冲区

package com.zhao.NIO.Buffer;
 ​
 import org.testng.annotations.Test;
 ​
 import java.nio.ByteBuffer;
 ​
 /*
     目标:对缓冲区Buffer的常用API进行案例实现
  */
 public class BufferTest {
 ​
         //创建直接缓冲区,使用 allocateDirect 验证是否是直接缓冲区使用 isDirect()
         @Test
         public void test(){
                 ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
                 System.out.println(buffer.isDirect());
         }
 ​
         @Test
         public void test02(){
                 //分配一个缓冲区
                 ByteBuffer buffer = ByteBuffer.allocate(10);
                 System.out.println(buffer.position());//0
                 System.out.println(buffer.limit());//10
                 System.out.println(buffer.capacity());//10
                 System.out.println("------------------------");
 ​
                 String name = "itheima";
                 buffer.put(name.getBytes());
                 System.out.println(buffer.position()); //7
                 System.out.println(buffer.limit()); //10
                 System.out.println(buffer.capacity()); //10
                 System.out.println("------------------------");
 ​
                 //2.clear清除缓冲区中的数据
                 buffer.clear();
                 System.out.println(buffer.position()); //0
                 System.out.println(buffer.limit()); //10
                 System.out.println(buffer.capacity()); //10
                 System.out.println((char) buffer.get());//得到 i  证明没有真正的清除数据,只是把position的位置恢复到第一个,只有后续的add添加数据才会将它的值一一覆盖
                 System.out.println("------------------------");
 ​
                 //3.定义一个缓冲区
                 ByteBuffer buf = ByteBuffer.allocate(10);
                 String n = "itheima";
                 buf.put(n.getBytes());
 ​
                 buf.flip();
 ​
                 //读取数据
                 byte[] b = new byte[2];
                 buf.get(b);
                 String rs = new String(b);
                 System.out.println(rs);
 ​
                 System.out.println(buf.position());//2
                 System.out.println(buf.limit());//7
                 System.out.println(buf.capacity());//10
                 System.out.println("--------------");
 ​
                 buf.mark();  //标记此刻这个位置!2
 ​
                 byte[] b2 = new byte[3];
                 buf.get(b2);
                 System.out.println(buf.position());//5
                 System.out.println(buf.limit());//7
                 System.out.println(buf.capacity());//10
 ​
                 buf.reset();   //回到标记位置
                 if (buf.hasRemaining()){    //是否有剩余 hasRemaining
                         System.out.println(buf.remaining());
                 }
         }
 ​
 ​
         @Test
         public void test01(){
 ​
         //分配一个缓冲区
         ByteBuffer buffer = ByteBuffer.allocate(10);
         System.out.println(buffer.position());//0
         System.out.println(buffer.limit());//10
         System.out.println(buffer.capacity());//10
         System.out.println("------------------------");
 ​
         //2.put往缓冲区中添加数据
         String name = "itheima";
         buffer.put(name.getBytes());
         System.out.println(buffer.position()); //7
         System.out.println(buffer.limit()); //10
         System.out.println(buffer.capacity()); //10
 ​
         System.out.println("------------------------");
 ​
 ​
         //3.Buffer flip()为将缓冲区的界限设置为当前位置,并将当前位置设值为 0 可读模式
         buffer.flip();
         System.out.println(buffer.position()); //7
         System.out.println(buffer.limit()); //10
         System.out.println(buffer.capacity()); //10
         System.out.println("------------------------");
 ​
         //4.get数据的读取
         char c = (char) buffer.get();
         System.out.println(c);
         System.out.println(buffer.position()); //
         System.out.println(buffer.limit()); //7
         System.out.println(buffer.capacity()); //10
     }
 }
 ​
3.3.6、直接与非直接内存

什么是直接内存与非直接内存

根据官方文档的描述:

byte byffer可以是两种类型,一种是基于直接内存(也就是非堆内存);另一种是非直接内存(也就是堆内存)。对于直接内存来说,JVM将会在IO操作上具有更高的性能,因为它 直接作用于本地系统的IO操作。而非直接内存,也就是堆内存中的数据,如果要作IO操作,会先从本进程内存复制到直接内存,再利用本地IO处理。

从数据流的角度,非直接内存是下面这样的作用链:

本地IO-->直接内存-->非直接内存-->直接内存-->本地IO

而直接内存是: 本地IO-->直接内存-->本地IO

很明显,在做IO处理时,比如网络发送大量数据时,直接内存会具有更高的效率。直接内存使用allocateDirect创建,但是它比申请普通的堆内存需要耗费更高的性能。不过,这部分的数据是在JVM之外的,因此它不会占用应用的内存。所以呢,当你有很大的数据要缓存,并且它的生命周期又很长,那么就比较适合使用直接内存。只是一般来说,如果不是能带来很明显的性能提升,还是推荐直接使用堆内存。字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。

使用场景

1 、有很大的数据需要存储,它的生命周期又很长

2 、适合频繁的IO操作,比如网络并发场景

3.4、NIO核心二:通道(Channel)

通道Channe概述

通道(Channel):由 java.nio.channels 包定义 的。Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

1、 NIO 的通道类似于流,但有些区别如下:

通道可以同时进行读写,而流只能读或者只能写通道可以实现异步读写数据通道可以从缓冲读数据,也可以写数据到缓冲:

2、 BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。

3 、Channel 在 NIO 中是一个接口

public interface Channel extends Closeable{}
常用的Channel实现类
  • FileChannel:用于读取、写入、映射和操作文件的通道。

  • DatagramChannel:通过 UDP 读写网络中的数据通道。

  • SocketChannel:通过 TCP 读写网络中的数据。

  • ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。【ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket】

FileChannel 类

获取通道的一种方式是对支持通道的对象调用getChannel() 方法。支持通道的类如下:

  • FileInputStream

  • FileOutputStream

  • RandomAccessFile

  • DatagramSocket

  • Socket

  • ServerSocket

    获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。或者通过通道的静态方法 open() 打开并返回指定通道

FileChannel的常用方法
int read(ByteBuffer dst) 从Channel到中读取数据到ByteBuffer
 long  read(ByteBuffer[] dsts) 将Channel到中的数据“分散”到ByteBuffer[]
 int  write(ByteBuffer src)将ByteBuffer 到中的数据写入到  Channel
 long write(ByteBuffer[] srcs)将ByteBuffer[] 到中的数据“聚集”到  Channel
 long position() 返回此通道的文件位置
 FileChannel position(long p) 设置此通道的文件位置
 long size() 返回此通道的文件的当前大小
 FileChannel truncate(long s) 将此通道的文件截取为给定大小
 void force(boolean metaData) 强制将所有对此通道的文件更新写入到存储设备中
案例1-本地文件写数据
package com.zhao.NIO.Channel;
 ​
 import org.testng.annotations.Test;
 ​
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 ​
 public class ChannelTest {
 ​
     @Test
     public void write(){
         try {
             //1.字节输出流通向目标文件
             FileOutputStream fos = new FileOutputStream("data01.txt");
             //2.得到字节输出流对应的channel通道
             FileChannel channel = fos.getChannel();
             //3.分配缓冲区
             ByteBuffer buffer = ByteBuffer.allocate(1024);
             buffer.put("你好,程序员!".getBytes());
             //4.把缓冲区切换成写出模式
             buffer.flip();
             channel.write(buffer);
             channel.close();
             System.out.println("写数据到文件");
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }
案例2-本地文件读数据
@Test
 public void read() throws Exception {
     //1.定义一个文件字节输入流与源文件相通
     FileInputStream is = new FileInputStream("data01.txt");
     //2.需要得到文件字节输入流的文件通道
     FileChannel channel = is.getChannel();
     //3.定义一个缓冲区
     ByteBuffer buffer = ByteBuffer.allocate(1024);
     //4.读取数据到缓冲区
     channel.read(buffer);
     buffer.flip();
     //5.读取出缓冲区中的数据并输出即可
     String rs = new String(buffer.array(),0, buffer.remaining());
     System.out.println(rs);
 }
案例3-使用Buffer完成文件复制
@Test
 public void copy() throws Exception {
     //源文件
     File srcFile = new File("/Users/mac/Desktop/未命名文件夹/tx.jpg");
     File destFile = new File("/Users/mac/Desktop/未命名文件夹/tx1.jpg");
     //得到一个字节输入流
     FileInputStream fis = new FileInputStream(srcFile);
     //得到一个字节输出流
     FileOutputStream fos = new FileOutputStream(destFile);
     //得到的数文件通道
     FileChannel isChannel = fis.getChannel();
     FileChannel osChannel = fos.getChannel();
     //分配缓冲区
     ByteBuffer buffer = ByteBuffer.allocate(1024);
     while (true){
         //必须先清空缓冲区然后在写入数据到缓冲区
         buffer.clear();
         //开始读取一次数据
         int flag = isChannel.read(buffer);
         if (flag == -1){
             break;
         }
         //已经读取到了数据,把缓冲区的模式切换成为可读模式
         buffer.flip();
         //把数据写出到
         osChannel.write(buffer);
     }
     isChannel.close();
     osChannel.close();
 ​
 }
案例4-分散 (Scatter) 和聚集 (Gather)

分散读取(Scatter ):是指把Channel通道的数据读入到多个缓冲区中去

聚集写入(Gathering )是指将多个 Buffer 中的数据“聚集”到 Channel。

@Test
 public void test() throws Exception {
     //1、字节输入管道
     FileInputStream is = new FileInputStream("data01.txt");
     FileChannel isChannel = is.getChannel();
     //2、字节输出流
     FileOutputStream fos = new FileOutputStream("data02.txt");
     FileChannel osChannel = fos.getChannel();
     //3、定义多个缓冲区做数据分散
     ByteBuffer buffer1 = ByteBuffer.allocate(4);
     ByteBuffer buffer2 = ByteBuffer.allocate(1024);
     ByteBuffer[] buffers = {buffer1,buffer2};
     //4.从通道中读取数据分散到各个缓冲区
     isChannel.read(buffers);
     //5.从每个缓冲区中查询是否有数据读到了
     for (ByteBuffer buffer : buffers) {
         buffer.flip();//切换到读数据模式
         System.out.println(new String(buffer.array(),0,buffer.remaining()));
     }
     //6.聚集写入到通道
     osChannel.write(buffers);
     isChannel.close();
     osChannel.close();
     System.out.println("文件聚集与分散~");
 }
案例5-transferFrom() transferTo()
@Test
 public void test02() throws Exception {
     //1、字节输入管道
     FileInputStream is = new FileInputStream("data01.txt");
     FileChannel isChannel = is.getChannel();
     //2、字节输出流
     FileOutputStream fos = new FileOutputStream("data04.txt");
     FileChannel osChannel = fos.getChannel();
     //3.复制数据
     //transferFrom
     //osChannel.transferFrom(isChannel,isChannel.position(),isChannel.size());  //data03.txt
     //transferTo
     isChannel.transferTo(isChannel.position(), isChannel.size(), osChannel);
     isChannel.close();
     osChannel.close();
     System.out.println("完成复制!");
 }

3.5、NIO核心三:选择器(Selector)

选择器(Selector)概述

选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心

  • Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)

  • Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。

  • 只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程

  • 避免了多线程之间的上下文切换导致的开销

选择器(Selector)的应用

创建 Selector :通过调用 Selector.open() 方法创建一个 Selector。

Selector selector = Selector.open();

向选择器注册通道:SelectableChannel.register(Selector sel, int ops)

//1. 获取通道
 ServerSocketChannel ssChannel = ServerSocketChannel.open();
 //2. 切换非阻塞模式
 ssChannel.configureBlocking(false);
 //3. 绑定连接
 ssChannel.bind(new InetSocketAddress(9898));
 //4. 获取选择器
 Selector selector = Selector.open();
 //5. 将通道注册到选择器上, 并且指定“监听接收事件”
 ssChannel.register(selector, SelectionKey.OP_ACCEPT);

当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。可以监听的事件类型(用 可使用 SelectionKey 的四个常量 表示):

  • 读 : SelectionKey.OP_READ (1)

  • 写 : SelectionKey.OP_WRITE (4)

  • 连接 : SelectionKey.OP_CONNECT (8)

  • 接收 : SelectionKey.OP_ACCEPT (16)

  • 若注册时不止监听一个事件,则可以使用“位或”操作符连接。

int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE

3.6、 NIO非阻塞式网络通信原理分析

Selector 示意图和特点说明

Selector可以实现: 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

服务端流程
  • 1、 当客户端连接服务端时,服务端会通过 ServerSocketChannel 得到 SocketChannel:1获取通道

ServerSocketChannel ssChannel = ServerSocketChannel.open();
  • 2、切换非阻塞模式

ssChannel.configureBlocking(false);
  • 3、 绑定连接

ssChannel.bind(new InetSocketAddress(9999));
  • 4、 获取选择器

Selector selector = Selector.open();
  • 5 、将通道注册到选择器上, 并且指定“监听接收事件”

ssChannel.register(selector, SelectionKey.OP_ACCEPT);
  • 6 、轮询式的获取选择器上已经“准备就绪”的事件

//轮询式的获取选择器上已经“准备就绪”的事件
    while (selector.select() > 0) {
           System.out.println("轮一轮");
           //7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
           Iterator<SelectionKey> it = selector.selectedKeys().iterator();
           while (it.hasNext()) {
               //8. 获取准备“就绪”的是事件
               SelectionKey sk = it.next();
               //9. 判断具体是什么事件准备就绪
               if (sk.isAcceptable()) {
                   //10. 若“接收就绪”,获取客户端连接
                   SocketChannel sChannel = ssChannel.accept();
                   //11. 切换非阻塞模式
                   sChannel.configureBlocking(false);
                   //12. 将该通道注册到选择器上
                   sChannel.register(selector, SelectionKey.OP_READ);
               } else if (sk.isReadable()) {
                   //13. 获取当前选择器上“读就绪”状态的通道
                   SocketChannel sChannel = (SocketChannel) sk.channel();
                   //14. 读取数据
                   ByteBuffer buf = ByteBuffer.allocate(1024);
                   int len = 0;
                   while ((len = sChannel.read(buf)) > 0) {
                       buf.flip();
                       System.out.println(new String(buf.array(), 0, len));
                       buf.clear();
                   }
               }
               //15. 取消选择键 SelectionKey
               it.remove();
           }
       }
   }
 ​
客户端流程

1、 获取通道

SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));

2、 切换非阻塞模式

sChannel.configureBlocking(false);

3、 分配指定大小的缓冲区

ByteBuffer buf = ByteBuffer.allocate(1024);

4、 发送数据给服务端

Scanner scan = new Scanner(System.in);
     while(scan.hasNext()){
           String str = scan.nextLine();
           buf.put((new SimpleDateFormat("yyyy/MM/dd             HH:mm:ss").format(System.currentTimeMillis())
         + "\n" + str).getBytes());
     buf.flip();
     sChannel.write(buf);
     buf.clear();
     }
     //关闭通道
     sChannel.close();

3.7、NIO非阻塞网络通信入门案例

需求:服务端接收客户端的连接请求,并接收多个客户端发送过来的事件。

代码实现
package com.zhao.Selector;
 ​
 import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.util.Iterator;
 ​
 /*
     目标:NIO非阻塞通信下的入门案例,服务端开发
  */
 public class Server {
     public static void main(String[] args) throws Exception {
         System.out.println("----服务端启动-----");
         //1.获得通道
         ServerSocketChannel ssChannel = ServerSocketChannel.open();
         //2.切换为非阻塞模式
         ssChannel.configureBlocking(false);
         //3.绑定连接的端口
         ssChannel.bind(new InetSocketAddress(9999));
         //4.获得选择器Selector
         Selector selector = Selector.open();
         //5.将通道都注册到选择器上去,并且开始指定监听接收事件
         ssChannel.register(selector, SelectionKey.OP_ACCEPT);
         //6.使用Selector选择器轮询已经就绪好的事件
         while (selector.select() > 0){
             System.out.println("开始一轮事件处理~~~");
             //7.获取选择器中的所有注册的通道中已经就绪好的事件
             Iterator<SelectionKey> it =  selector.selectedKeys().iterator();
             //8.开始遍历这些准备好的事件
             while (it.hasNext()){
                 //提取当前这个事件
                 SelectionKey sk = it.next();
                 //9.判断这个事件具体是什么
                 if (sk.isAcceptable()){
                     //10.直接获取当前接入的客户端通道
                     SocketChannel schannel = ssChannel.accept();
                     //11.切换成非阻塞模式
                     schannel.configureBlocking(false);
                     //12.将本客户端通道注册到选择器
                     schannel.register(selector,SelectionKey.OP_READ);
                 }else if (sk.isReadable()){
                     //13.获取当前选择器上的就绪事件
                     SocketChannel sChannel = (SocketChannel) sk.channel();
                     //14.读取数据
                     ByteBuffer buf = ByteBuffer.allocate(1024);
                     int len = 0;
                     while ((len = sChannel.read(buf)) > 0){
                         buf.flip();
                         System.out.println(new String(buf.array(),0,len));
                         buf.clear();//清除之前的数据
                     }
                 }
                 it.remove();//处理完毕之后需啊哟移除当前事件
             }
         }
     }
 }
package com.zhao.Selector;
 ​
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.SocketChannel;
 import java.util.Scanner;
 ​
 /*
     目标:客户端案例实现~基于NIO非阻塞通信
  */
 public class Client {
     public static void main(String[] args) throws Exception {
         //1.获得通道
         SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9999));
         //2.切换到非阻塞模式
         sChannel.configureBlocking(false);
         //3.分配指定缓冲区大小
         ByteBuffer buf = ByteBuffer.allocate(1024);
         //4.发送数据给服务端
         Scanner scan = new Scanner(System.in);
         while (true){
             System.out.println("请说:");
             String msg = scan.nextLine();
             buf.put(("波仔:"+msg).getBytes());
             buf.flip();
             sChannel.write(buf);
             buf.clear();
         }
     }
 }

3.8、 NIO 网络编程应用实例-群聊系统

需求:进一步理解 NIO 非阻塞网络编程机制,实现多人群聊

  • 编写一个 NIO 群聊系统,实现客户端与客户端的通信需求(非阻塞)

  • 服务器端:可以监测用户上线,离线,并实现消息转发功能

  • 客户端:通过 channel 可以无阻塞发送消息给其它所有客户端用户,同时可以接受其它客户端用户通过服务端转发来的消息

服务端

package com.zhao.nio_chat;
 ​
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.*;
 import java.util.Iterator;
 ​
 /*
     目标:服务端群聊系统实现.
  */
 public class Server {
     //1.定义一些成员属性变量:选择器。服务端通道、端口
     private Selector selector;
     private ServerSocketChannel ssChannel;
     private static final int PORT = 9999;
 ​
     //2.定义初始化代码逻辑
     public Server(){
         try {
             //a.创建选择器对象
             selector = Selector.open();
             //b.获取通道
             ssChannel = ServerSocketChannel.open();
             //c.绑定客户端连接的端口
             ssChannel.bind(new InetSocketAddress(PORT));
             //d.设置非阻塞通信模式
             ssChannel.configureBlocking(false);
             //e.把通道注册到选择器上面去,并且开始指定连接事件
             ssChannel.register(selector, SelectionKey.OP_ACCEPT);
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 /*
     开始监听
  */
     private void listen() {
         try {
             while (selector.select() > 0){
                 //a.获取选择器中所有注册通道的就绪事件
                 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                 //b.开始遍历这些事件
                 while (it.hasNext()){
                     //提取这个事件
                     SelectionKey sk = it.next();
                     //c.判断这个事件的类型
                     if (sk.isAcceptable()){
                         //客户端接入请求
                         //获取当前客户端通道
                         SocketChannel schannel = ssChannel.accept();
                         //注册成非阻塞模式
                         schannel.configureBlocking(false);
                         //注册选择器,监听读数据的事件
                         schannel.register(selector,SelectionKey.OP_READ);
                     }else if (sk.isReadable()){
                         //处理这个客户端的消息,接收他然后实现转发逻辑
                         readClientData(sk);
                     }
                     it.remove();//处理完毕之后需要移除当前事件!
                 }
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 /*
     接收当前客户端通道的消息,转发给其他全部客户端通道
  */
     private void readClientData(SelectionKey sk) {
         SocketChannel sChannel = null;
 ​
         try {
             //直接得到当前客户端通道
             sChannel = (SocketChannel) sk.channel();
             //创建缓冲区对象开始接收客户端通道的数据
             ByteBuffer buffer = ByteBuffer.allocate(1024);
             int count = sChannel.read(buffer);
             if (count > 0){
                 buffer.flip();
                 //得到读取到的数据
                 String msg = new String(buffer.array(),0,buffer.remaining());
                 System.out.println("接收到了客户端的消息:"+msg);
                 //把这个消息推送给全部客户端接收
                 sendMsgToAllClient(msg,sChannel);
             }
         } catch (Exception e) {
             try {
                 System.out.println("有人离线了"+sChannel.getRemoteAddress());
                 //当前客户端离线
                 sk.cancel();//取消注册
                 sChannel.close();
             } catch (Exception ex) {
                 ex.printStackTrace();
             }
         }
     }
 /*
     把当前客户端的消息数据推送给当前全部在线注册的channel
  */
     private void sendMsgToAllClient(String msg, SocketChannel sChannel) throws IOException {
         System.out.println("服务端开始转发这个消息:当前处理的线程:"+Thread.currentThread().getName());
         for (SelectionKey key : selector.keys()) {
             Channel channel =  key.channel();
             //不要把数据发给了自己
             if (channel instanceof SocketChannel && channel != sChannel){
                 ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                 ((SocketChannel)channel).write(buffer);
             }
         }
     }
 ​
 ​
     public static void main(String[] args) {
         //创建服务端对象
         Server server = new Server();
         //开始监听客户端的各种消息事件:连接、群聊消息、离线消息
         server.listen();
     }
 ​
 }

客户端

package com.zhao.nio_chat;
 ​
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
 import java.nio.channels.SocketChannel;
 import java.util.Iterator;
 import java.util.Scanner;
 ​
 /*
     目标:客户端代码逻辑的实现
  */
 public class Client {
 ​
     //1.定义客户端的相关属性
     private Selector selector;
     private static int PORT = 9999;
     private SocketChannel socketChannel;
 ​
     //2.初始化客户端信息
     public Client(){
         try {
             //a.创建服务器
             selector = Selector.open();
             //b.连接服务端
             socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",PORT));
             //c.设置非阻塞模式
             socketChannel.configureBlocking(false);
             socketChannel.register(selector, SelectionKey.OP_READ);
             System.out.println("当前客户端准备完成:");
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 ​
     private void readInfo() throws IOException {
         while (selector.select() > 0){
             Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
             while (iterator.hasNext()){
                 SelectionKey key = iterator.next();
                 if (key.isReadable()){
                     SocketChannel sc = (SocketChannel) key.channel();
                     ByteBuffer buffer = ByteBuffer.allocate(1024);
                     sc.read(buffer);
                     System.out.println(new String(buffer.array()).trim());
                     System.out.println("--dsd----------");
                 }
                 iterator.remove();
             }
         }
     }
 ​
     public static void main(String[] args) throws IOException {
         Client client = new Client();
         new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     client.readInfo();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }).start();
 ​
         Scanner sc = new Scanner(System.in);
         while (true){
             System.out.println("----------");
             String s = sc.nextLine();
             client.sendToServer(s);
         }
     }
 ​
     private void sendToServer(String s) throws IOException {
         socketChannel.write(ByteBuffer.wrap(("波仔说:"+s).getBytes()));
     }
 ​
 }

4.1、Java AIO深入剖析

Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

AIO 异步非阻塞,基于NIO的,可以称之为NIO2.0 BIO

BIONIOAIO
SocketSocketChannel AsynchronousSocketChannel
ServerSocket ServerSocketChannel AsynchronousServerSocketChannel

与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可, 这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序即可以理解read/write方法都是异步的,完成后会主动调用回调函数。在JDK1.7中,这部分内容被称作NIO.2,主要在Java.nio.channels包下增加了下面四个异步通道:

  • AsynchronousSocketChannel

  • AsynchronousServerSocketChannel

  • AsynchronousFileChannel

  • AsynchronousDatagramChannel

总结

BIO、NIO、AIO:
  • Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

  • Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

  • Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

BIO、NIO、AIO适用场景分析:
  • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

  • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

  • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。Netty!

Netty!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值