网站自然排名怎么,分析网络营销方式,在哪个网站做简历比较好,折纸效果网站文章目录IO模型Java BIOJava NIOJava AIO#xff08;NIO.2#xff09;BIO、NIO、AIO的使用场景BIO1 BIO 基本介绍2 BIO 的工作机制3 BIO 传统通信实现3.1 业务需求3.2 实现思路3.3 代码实现4 BIO 模式下的多发和多收消息4.1 业务需求4.2 实现思路4.3 代码实现5 BIO 模式下接收…
文章目录IO模型Java BIOJava NIOJava AIONIO.2BIO、NIO、AIO的使用场景BIO1 BIO 基本介绍2 BIO 的工作机制3 BIO 传统通信实现3.1 业务需求3.2 实现思路3.3 代码实现4 BIO 模式下的多发和多收消息4.1 业务需求4.2 实现思路4.3 代码实现5 BIO 模式下接收多个客户端5.1 业务需求5.2 实现思路5.3 代码实现6 BIO伪异步IO编程6.1 业务需求6.2 实现思路6.3 代码实现7 基于BIO形式下的文件上传7.1 业务需求7.2 实现思路7.3 代码实现学习渠道IO模型
Java BIO
同步并阻塞是传统阻塞型服务事项模式为一个客户端连接一个线程也就是客户端有连接请求时服务器端就需要启动一个线程进行处理如果这个连接不做任何事情会造成不必要的线程开销。
Java NIO
同步非阻塞服务实现模式为一个线程处理多个请求连接即客户端发送的连接请求都会注册到多路复用器也可以说是Selector选择器上多路复用器轮询到连接有IO请求就进行处理如果没有请求是可以做其他事情的。
Java AIONIO.2
异步非阻塞服务器实现模式为一个有效请求一个线程客户端的IO请求都是由OS先完成了再通知服务器应用去启动线程进行处理一般适用于连接数较多且连接时间较长的应用
BIO、NIO、AIO的使用场景
BIO 适用于连接数目比较小且固定的架构这种方式对服务器资源要求比较高并发局限于应用中。JDK4以前的唯一选择但是代码比较好理解
NIO 适用于链接数目较多且连接时长比较短轻操作的架构比如聊天服务器、弹幕系统、服务器间通信等。编程比较复杂JDK4开始出现
AIO 方式适用于连接山姆较多且连接时长较长重操作的架构比如相册服务器、充分调用OS参与并发操作。编程比较复杂JDK7开始出现。
BIO
1 BIO 基本介绍
Java BIO 就是传统的 java io 编程其相关的类和接口在 java.io
BIO 可以通过线程池进行改善
2 BIO 的工作机制
对 BIO 编程流程的梳理
服务器端启动一个 ServerSocket注册端口调用accpet方法监听客户端的Socket连接。客户端启动 Socket 对服务器进行通信默认情况下服务器端需要对每个客户 建立一个线程与之通讯 3 BIO 传统通信实现
3.1 业务需求
客户端向服务器端发送一串文字
3.2 实现思路
客户端启动 Socket 对服务器进行通信默认情况下服务器端需要对每个客户 建立一个线程与之通讯
服务器端启动一个 ServerSocket注册端口调用accpet方法监听客户端的Socket连接。
3.3 代码实现
客户端
创建socket对象请求服务器连接从socket对象中获取一个字节输出流把字节流改装成自己需要的流进行数据的发送开始发送消息
public class ClientBIO {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);//4、 开始发送消息ps.println(hello!);// 刷新流中可能存在或可能不存在的任何元素的流ps.flush();}
}服务器端
定义一个ServiceSocket对象进行服务端的端口注册监听客户端的Socket连接请求得到一个端到端的Socket管道从Socket管道中得到一个字节输入流。把字节输入流包装成一个缓冲字符输入流读取缓冲流中的数据如果缓冲流中还有数据继续读取数据如果缓冲流中没有数据
public class ServerBIO {public static void main(String[] args) {try {System.out.println(服务器端启动了);// 1 定义一个ServiceSocket对象进行服务端的端口注册ServerSocket ss new ServerSocket(9999);// 2 监听客户端的Socket连接请求Socket socket ss.accept();// 3 从Socket管道中得到一个自己输入流对象InputStream is socket.getInputStream();// 4 把字节输入流包装成一个缓冲字符输入流将字节输入流转为字符输入流BufferedReader br new BufferedReader(new InputStreamReader(is));String msg;while ((msg br.readLine())!null) {System.out.println(服务器端接收到的 msg);}} catch (IOException e) {e.printStackTrace();}}
}这里代码有两个问题
第一个问题发生在客户端中。 这条代码 ps.print(hello!); 传入到服务器中就会出现 Connection reset 发生异常阻塞。原因如下由于在服务器中以行作为结束标记。因此 hello 在传入后并没有换行于是 while 语句继续等待。此时客户端中hello 语句发完后客户端线程已经关闭此时服务器端在监听是发现与客户端的socket连接断开于是就发生了 Connection reset
第二个发生在服务器段中。当纠正了 ps.print(hello!); 为 ps.println(hello!); 后传入到服务器中由于客户端发送完毕后就断开了socket连接而服务器端则在while中等待是否有下一条语句到来此时监听到socket连接已经断开就报异常。解决的方法是将 while ((msg br.readLine())!null) 修改为 if ((msg br.readLine())!null)也就是说客户端如果只发送一次数据那么服务器端也只接受一次。
4 BIO 模式下的多发和多收消息
4.1 业务需求
实现客户端可以反复发送消息和服务器端可以持续接收消息
4.2 实现思路
客户端启动 Socket 对服务器进行通信默认情况下服务器端需要对每个客户 建立一个线程与之通讯。将输入数据和发送数据放在一个循环嵌套之中。
服务器端启动一个 ServerSocket注册端口调用accpet方法监听客户端的Socket连接。使用while持续监听如果有数据进行数据没有数据保持等待
4.3 代码实现
客户端
创建socket对象请求服务器连接从socket对象中获取一个字节输出流把字节流改装成自己需要的流进行数据的发送输入数据和发送数据放在一个循环嵌套之中
public class ClientBIO {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 sc new Scanner(System.in);while(true) {System.out.print(请说);String msg sc.nextLine();ps.println(msg);ps.flush();}}
}服务器端
定义一个ServiceSocket对象进行服务端的端口注册监听客户端的Socket连接请求得到一个端到端的Socket管道从Socket管道中得到一个字节输入流。把字节输入流包装成一个缓冲字符输入流使用while持续监听readLine()是一个阻塞函数,当没有数据读取时,就一直会阻塞在那如果有数据进行数据没有数据保持等待
public class ServerBIO {public static void main(String[] args) {try {System.out.println(服务器端启动了);// 1 定义一个ServiceSocket对象进行服务端的端口注册ServerSocket ss new ServerSocket(9999);// 2 监听客户端的Socket连接请求Socket socket ss.accept();// 3 从Socket管道中得到一个自己输入流对象InputStream is socket.getInputStream();// 4 把字节输入流包装成一个缓冲字符输入流将字节输入流转为字符输入流BufferedReader br new BufferedReader(new InputStreamReader(is));String msg;while ((msg br.readLine())!null) {System.out.println(服务器端接收到的 msg);}} catch (IOException e) {e.printStackTrace();}}
}5 BIO 模式下接收多个客户端
5.1 业务需求
服务端需要处理很多个客户端的消息通信请求同时要求每个请求都需要一个对应的线程去实现请求
5.2 实现思路
客户端不需要变但是在idea编译器中需要打开“allow multiple instances”
服务器端在循环中启动对应的线程线程里是解决方法
5.3 代码实现
客户端
public class ClientBIO {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 sc new Scanner(System.in);while(true) {System.out.print(请说);String msg sc.nextLine();ps.println(msg);ps.flush();}}
}服务器端
public class ServerBIO {public static void main(String[] args) {try {System.out.println(服务器端启动了);// 1 定义一个ServiceSocket对象进行服务端的端口注册ServerSocket ss new ServerSocket(9999);while (true) {Socket socket ss.accept();new ServerThreadReader(socket).start();}} catch (IOException e) {e.printStackTrace();}}
}对应线程
public class ServerThreadReader extends Thread{private Socket socket;public ServerThreadReader(Socket socket) {this.socket socket;}Overridepublic void run() {//3 从Socket管道中得到一个自己输入流对象try {InputStream is socket.getInputStream();// 4 把字节输入流包装成一个缓冲字符输入流将字节输入流转为字符输入流BufferedReader br new BufferedReader(new InputStreamReader(is));String msg;while ((msg br.readLine())!null) {System.out.println(服务器端接收到的 msg);}} catch (IOException e) {e.printStackTrace();}}
}小结其实就是把在服务器中的处理方法放到了线程中去处理
6 BIO伪异步IO编程
在接受多个客户端时存在这样一个问题随着客户端的并发访问增加时。服务端将呈现1:1的线程开销访问量越大系统将发生线程栈溢出线程创建失败最终导致进程宕机或者僵死从而不能对外提供服务。
解决方案是搭建伪异步I/O的通信框架。采用线程池和任务队列实现当客户端接入时将客户端的 Socket 封装成一个Task(该任务实现 java.lang.Runnable 线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息队列和N个活跃的线程对消息队列中Socket任务进行处理由于线程池可以设置消息队列的大小和最大线程数因此它的资源占用是可控的无论多少个客户端并发访问都不会导致资源的耗尽和宕机。
6.1 业务需求
使用有限个线程去处理多个客户端的请求。
6.2 实现思路
客户端不变
增加一个线程池处理类
服务器端调用线程池处理类
写一个处理的线程
6.3 代码实现
客户端
public class ClientBIO {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 sc new Scanner(System.in);while(true) {System.out.print(请说);String msg sc.nextLine();ps.println(msg);ps.flush();}}
}线程池处理类
// 线程池处理类
public class HandlerSocketThreadPool {// 线程池 private ExecutorService executor;public HandlerSocketThreadPool(int maxPoolSize, int queueSize){this.executor new ThreadPoolExecutor(3, // 8maxPoolSize, 120L, TimeUnit.SECONDS,new ArrayBlockingQueueRunnable(queueSize) );}public void execute(Runnable task){this.executor.execute(task);}
}服务器端
public class Server {public static void main(String[] args) {try {System.out.println(----------服务端启动成功------------);ServerSocket ss new ServerSocket(9999);// 一个服务端只需要对应一个线程池HandlerSocketThreadPool handlerSocketThreadPool new HandlerSocketThreadPool(3, 1000);// 客户端可能有很多个while(true){Socket socket ss.accept() ; // 阻塞式的System.out.println(有人上线了);// 每次收到一个客户端的socket请求都需要为这个客户端分配一个// 独立的线程 专门负责对这个客户端的通信handlerSocketThreadPool.execute(new ReaderClientRunnable(socket));}} catch (Exception e) {e.printStackTrace();}}}线程处理类
class ReaderClientRunnable implements Runnable{private Socket socket ;public ReaderClientRunnable(Socket socket) {this.socket socket;}Overridepublic void run() {try {// 读取一行数据InputStream is socket.getInputStream() ;// 转成一个缓冲字符流Reader fr new InputStreamReader(is);BufferedReader br new BufferedReader(fr);// 一行一行的读取数据String line null ;while((line br.readLine())!null){ // 阻塞式的System.out.println(服务端收到了数据line);}} catch (Exception e) {System.out.println(有人下线了);}}
}伪异步io采用了线程池实现因此避免了为每个请求创建一个独立线程造成线程资源耗尽的问题但由于底层依然是采用的同步阻塞模型因此无法从根本上解决问题。如果单个消息处理的缓慢或者服务器线程池中的全部线程都被阻塞那么后续socket的i/o消息都将在队列中排队。新的Socket请求将被拒绝客户端会发生大量连接超时。
7 基于BIO形式下的文件上传
7.1 业务需求
支持任意类型文件形式的上传。
7.2 实现思路
客户端在原来的基础上需要选择上传文件然后添加缓冲池因为考虑到文件可能过大
服务器端则需要选择保存的位置然后从缓冲池中获取数据
7.3 代码实现
客户端
public class Client {public static void main(String[] args) {try(InputStream is new FileInputStream(E:\\note\\01-java\\img\\JavaBIO工作机制.png);){// 1、请求与服务端的Socket链接Socket socket new Socket(127.0.0.1 , 8888);// 2、把字节输出流包装成一个数据输出流DataOutputStream dos new DataOutputStream(socket.getOutputStream());// 3、先发送上传文件的后缀给服务端dos.writeUTF(.png);// 4、把文件数据发送给服务端进行接收byte[] buffer new byte[1024];int len;while((len is.read(buffer)) 0 ){dos.write(buffer , 0 , len);}dos.flush();Thread.sleep(10000);}catch (Exception e){e.printStackTrace();}}
}服务器端
public class Server {public static void main(String[] args) {try{ServerSocket ss new ServerSocket(8888);while (true){Socket socket ss.accept();// 交给一个独立的线程来处理与这个客户端的文件通信需求。new ServerReaderThread(socket).start();}}catch (Exception e){e.printStackTrace();}}
}线程处理类
public class ServerReaderThread extends Thread {private Socket socket;public ServerReaderThread(Socket socket){this.socket socket;}Overridepublic void run() {try{// 1、得到一个数据输入流读取客户端发送过来的数据DataInputStream dis new DataInputStream(socket.getInputStream());// 2、读取客户端发送过来的文件类型String suffix dis.readUTF();System.out.println(服务端已经成功接收到了文件类型 suffix);// 3、定义一个字节输出管道负责把客户端发来的文件数据写出去OutputStream os new FileOutputStream(E:\\note\\01-java\\server\\img\\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 (Exception e){e.printStackTrace();}}
}客户端怎么发服务端就怎么接收
学习渠道
黑马程序员NIOBIO全套教程Java教程之IO模式精讲