【java】BIO 忘是亡心i 2022-09-03 01:13 113阅读 0赞 ### 文章目录 ### * 1 BIO * * 1.1 定义 * 1.2 使用场景 * 1.3 工作机制 * 2 java实现简单数据传输 * * 2.1 BIO下的多发和多收机制 * 2.2 BIO模式下接受多个客户端 * 2.3 BIO总结 * 3 伪异步I/O编程 * * 3.1 服务端类 * 3.2 线程池类 * 3.3 任务对象类 * 3.4 客户端类 * 3.5 总结 * 4 BIO实现任意文件上传 * * 4.1 客户端 * 4.2 服务端 * 4.3 服务端封装类 * 5 java BIO模式下的端口转发思想 * * 5.1 服务层 * 5.2 线程对象封装类 * 5.3 客户端 > 馄饨高兴的去面试,最近看了ThreadLocal的原理,知道内存泄漏是怎么产生的,心想,问到这里的时候又能说出一二了。 > 面试官:那我就先考考你基础吧,IO听过吗? > 我心想:不会吧,IO这里我就用过文件上传,心想面试官是不是要抓住IO这好好考。 > 我:了解过 > 面试官:那你说说BIO、NIO、AIO有什么区别吧。 > 我:BIO为同步阻塞、NIO为同步非阻塞、AIO为异步非阻塞 > 面试官:恩恩,那你对它们了解多少? > 我:我就知道个概念。 > 面试官:恩恩,好,回去等通知吧。 > 凉凉月色为你等待通知 ~ # 1 BIO # ## 1.1 定义 ## 同步并阻塞,服务器实现模式为一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程,如果这个连接不做任何事情会造成不必要的线程开销。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JsYWNrX0N1c3RvbWVy_size_16_color_FFFFFF_t_70] ## 1.2 使用场景 ## **BIO** 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。 ## 1.3 工作机制 ## ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JsYWNrX0N1c3RvbWVy_size_16_color_FFFFFF_t_70 1] **客户端** 1.创建socket对象通通过ip和端口向服务端建立连接 2.获取IO流数据 3.发送数据 **服务端** 1.创建Socket对象进行服务端端口注册 2.监听客户端的Socket链接请求 3.获取IO数据 4.输出数据 # 2 java实现简单数据传输 # 我们通过最简单的Java代码实现客户端服务端的数据发送,我们创建Server服务端类和Client客户端类。 import java.io.*; import java.net.Socket; public class Client { public static void main(String[] args) { System.out.println("客户端开始创建连接"); try { //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 服务端你好"); ps.flush(); } catch (IOException e) { e.printStackTrace(); } } } public class Server { public static void main(String[] args) { System.out.println("服务端启动"); try { //1.创建ServerSocket的实例进行服务端的端口注册 ServerSocket ss = new ServerSocket(9999); //2.监听客户端的Socket链接请求 Socket socket = ss.accept(); //3.从Socket管道中得到一个字节输入流对象 InputStream inputStream = socket.getInputStream(); //4.把字节输入流包装成一个缓冲字符输入流 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String str; if((str = bufferedReader.readLine()) != null) { System.out.println("服务端接收到数据" + str); } } catch (IOException e) { e.printStackTrace(); } } } 这里我们发现客户端发送的时候发送的是一行字符,如果我服务端`if((str = bufferedReader.readLine()) != null)`的`if`用`while`,那么服务端会一直等待客户端发送的数据,但是客户端发送完一条数据就结束了,这时候服务端在等待时候发现客户端已经结束,那么服务端就会报错,去进行重试。 > 服务端启动 > 服务端接收到数据hello word 服务端你好 > java.net.SocketException: Connection reset **这个只适用于发送一行数据** ## 2.1 BIO下的多发和多收机制 ## 客户端可以反复的发送数据,服务端可以反复的接收数据 我们发现在`server`层中只需要用`while`循环来一直接收客户端的数据就可以了,那么我们改进客户端,将客户端的数据改为可以多次发送 import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** * @ClassName Client * @Description * @Author asus * @Date 2021/8/3 9:04 * @Version 1.0 **/ public class Client { public static void main(String[] args) { System.out.println("客户端开始创建连接"); try { //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){ String msg = sc.nextLine(); System.out.print("发送数据....."); ps.println(msg); ps.flush(); } } catch (IOException e) { e.printStackTrace(); } } } import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * @ClassName Server * @Description * @Author asus * @Date 2021/8/3 9:04 * @Version 1.0 **/ public class Server { public static void main(String[] args) { System.out.println("服务端启动"); try { //1.创建ServerSocket的实例进行服务端的端口注册 ServerSocket ss = new ServerSocket(9999); //2.监听客户端的Socket链接请求 Socket socket = ss.accept(); //3.从Socket管道中得到一个字节输入流对象 InputStream inputStream = socket.getInputStream(); //4.把字节输入流包装成一个缓冲字符输入流 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String str; while ((str = bufferedReader.readLine()) != null) { System.out.println("服务端接收到数据" + str); } } catch (IOException e) { e.printStackTrace(); } } } 我们可以同时开两个客户端,然后向服务端进行发送。 先用客户端一发送 ![在这里插入图片描述][43bc383fdcb94c67af1a2596f07a1c89.png] ![在这里插入图片描述][9df1323dbe61450bb0c441bf29e2db1e.png] ![在这里插入图片描述][7c26ef611a894c3b89fbacfa5fbbff33.png] ![在这里插入图片描述][a6697ab6cab44be69180ace4090c6c84.png] 很明显我们发现服务端只能接收一个客户端的请求,第二个不能,这个是因为服务端在用`Socket socket = ss.accept();`接受连接后,就会跳转到`while ((str = bufferedReader.readLine()) != null)`来一直接收客户端一的请求,使得在客户端二启动后,根本执行不到`Socket socket = ss.accept();`这一步,当然服务端也就接收不到客户端二发送的请求了。 服务端只能处理一个客户端的请求,因为服务端是单线程。一次只能与一个客户端进行消息通信。 ## 2.2 BIO模式下接受多个客户端 ## 我们在上面发现,我一个服务端只能允许一个客户端去发送请求,那么怎么实现服务端接收多个客户端的请求呢,我们可以创建多个线程来进行客户端的接收。 客户端 package bio_three; import java.io.IOException; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** * @ClassName Client * @Description * @Author asus * @Date 2021/8/3 20:22 * @Version 1.0 **/ public class Client { public static void main(String[] args) { try { //1.对服务端进行ip认证 Socket socket = new Socket("127.0.0.1",9999); //2.得到一个打印流 PrintStream ps = new PrintStream(socket.getOutputStream()); //3.利用死循环不断的发送消息给客户端 Scanner scanner = new Scanner(System.in); while (true){ System.out.print("请说:"); String msg = scanner.nextLine(); ps.println(msg); ps.flush(); } } catch (IOException e) { e.printStackTrace(); } } } 创建服务端的接收客户端数据的类,继承自线程类 package bio_three; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; /** * @ClassName ServerThreadReader * @Description * @Author asus * @Date 2021/8/3 20:05 * @Version 1.0 **/ 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 bio_three; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * @ClassName Server * @Description * @Author asus * @Date 2021/8/3 20:02 * @Version 1.0 **/ public class Server { public static void main(String[] args) { try { //1.注册端口 ServerSocket serverSocket = new ServerSocket(9999); //2.定义一个死循环,负责不断的接收客户端的请求 while (true){ Socket socket = serverSocket.accept(); //创建一个独立的线程来处理与这个客户端的socket new ServerThreadReader(socket).start(); } } catch (IOException e) { e.printStackTrace(); } } } ![在这里插入图片描述][da1df5d5e2b442d18dd0c80436f042da.png] ![在这里插入图片描述][c929bd15081d449dbce667fbfdc50a42.png] ![在这里插入图片描述][33ec3845b4c54677a39bdff05d44c44b.png] 我们会发现,在多个客户端发送的时候,服务端通过创建多个线程,让线程管理来实现一个服务对应多个客户端进行数据的接收。 ## 2.3 BIO总结 ## 虽然我们实现了服务端接收多个客户端发送的请求,但是也存在一些问题。 * 1.每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能; * 2.每个线程都会占用栈空间和CPU资源; * 3.并不是每个socket都进行IO操作,无意义的线程处理; * 4.客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。 # 3 伪异步I/O编程 # 我们在BIO模式中发现,每次需要一个客户端请求,那么服务端这里就会创建一个线程,但是线程过多的情况下,服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。 我们可以通过线程池和任务队列来实现,当客户端的socket封装成一个Task(该任务实现java.lang.Runnable线程任务接口)交给后端的线程池进行处理。JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中Socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,所以它占用的资源是可控制的,无论多少个客户端并发访问,都不会导致资源的耗尽而宕机。 但是,因为线程池是限制线程数量的,所以也导致客户端连接的数量是有限的,在线程满的情况下,只有等到一个客户端用完释放的时候,其他客户端才能在对服务端进行请求。 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u4oyOfql-1628077251062)(../images/image-20210803210019362.png)\]][img-u4oyOfql-1628077251062_.._images_image-20210803210019362.png] 我们使用线程池的方式来处理客户端的请求,我们将服务端的socket对象封装成一个任务对象,交给线程池来进行任务的处理。 ## 3.1 服务端类 ## 首先我们创建服务端类 package bio_four; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * @ClassName Server * @Description * @Author asus * @Date 2021/8/3 21:28 * @Version 1.0 **/ public class Server { public static void main(String[] args) { try { //1.创建ServerSocket注册端口 ServerSocket ss = new ServerSocket(9999); //2.定义一个循环接收客户端的socket连接请求 //初始化一个线程池对象 HandlerSocketServerPool pool = new HandlerSocketServerPool(3,10); while (true){ Socket socket = ss.accept(); //3.把socket对象交给一个线程池管理 //把socket封装成一个任务对象交给线程池处理 Runnable target = new ServerRunnableTarget(socket); pool.execute(target); } } catch (IOException e) { e.printStackTrace(); } } } 我们会发现,我们在注册端口后,我们创建了HandlerSocketServerPool线程池,用它来进行任务的处理 ## 3.2 线程池类 ## 我们来看封装的线程池 package bio_four; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * @ClassName HandlerSocketServerPool * @Description * @Author asus * @Date 2021/8/3 21:20 * @Version 1.0 **/ public class HandlerSocketServerPool { //1.创建一个线程池的成员变量用于存储一个线程池对象 private ExecutorService executorService; /** * 2.创建这个类的对象的时候就需要初始化线程池对象 * public ThreadPoolExecutor(int corePoolSize, * int maximumPoolSize, * long keepAliveTime, * TimeUnit unit, * BlockingQueue<Runnable> workQueue) * */ public HandlerSocketServerPool(int maxThreadNum,int queueSize){ //ArrayBlockingQueue阻塞队列 executorService = new ThreadPoolExecutor( 3, maxThreadNum, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueSize)); } /** * 3、提供一个方法来提交任务给线程池的队列来暂存,等着线程池来处理 */ public void execute(Runnable runnable){ executorService.execute(runnable); } } 首先,我们创建ExecutorService 来进行线程池对象的存储,在构造方法中初始化一个线程池来赋值给ExecutorService 对象,我们创建一个execute方法将任务放入线程池的任务队列中,等待线程池进行任务的处理。 ## 3.3 任务对象类 ## 然后我们将socket对象封装成为一个任务对象,让线程池来执行这个任务。 package bio_four; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * @ClassName ServerRunnableTarget * @Description 实现Runnable接口来定义任务对象 * @Author asus * @Date 2021/8/3 21:42 * @Version 1.0 **/ public class ServerRunnableTarget implements Runnable{ private Socket socket; public ServerRunnableTarget(Socket socket) { this.socket = socket; } @Override public void run() { //处理接收到客户端Socket通信需求 try { //1.从Socket管道中得到一个字节输入流对象 InputStream inputStream = socket.getInputStream(); //2.把字节输入流包装成一个缓冲字符输入流 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String str; if ((str = bufferedReader.readLine()) != null) { System.out.println("服务端接收到数据" + str); } } catch (IOException e) { e.printStackTrace(); } } } 这个跟前面的服务端处理请求是一样的。 接下来我们将封装的任务队列交给线程池进行处理,也就是下面这个步骤。 Runnable target = new ServerRunnableTarget(socket); pool.execute(target); ## 3.4 客户端类 ## 这个跟以前的一样 public class Client { public static void main(String[] args) { try { // 1.简历一个与服务端的Socket对象:套接字 Socket socket = new Socket("127.0.0.1", 9999); // 2.从socket管道中获取一个输出流,写数据给服务端 OutputStream os = socket.getOutputStream() ; // 3.把输出流包装成一个打印流 PrintWriter pw = new PrintWriter(os); // 4.反复接收用户的输入 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = null ; while((line = br.readLine()) != null){ pw.println(line); pw.flush(); } } catch (Exception e) { e.printStackTrace(); } } } ## 3.5 总结 ## * 伪异步IO采用了线程池的实现,所以避免了我们一直创建线程而导致资源的耗尽,但因为底层还是采用的同步阻塞模型,因此无法从根本上解决问题。 * 如果单个消息处理的缓慢,或者服务器线程池中的全部线程都被阻塞,那么后续socket的i/o消息都将在队列中排队。新的Socket请求将被拒绝,客户端会发生大量连接超时。 # 4 BIO实现任意文件上传 # 我们通过客户端和服务端之间配合,来进行文件的发送 ## 4.1 客户端 ## 我们进行文件的上传,首先我们要获取客户端的文件,我这里是放在E盘的文件目录下,通过`DataOutputStream`方便文件的上传,`DataOutputStream`可以分段传输,比如我们向客户端可以先传文件的格式,用`dos.writeUTF(".txt");`来进行文件格式的指定,然后在发送文件,在发送完后,我们用`socket.shutdownOutput();`来通知服务端数据已经发送完毕。 package file; import javax.xml.crypto.Data; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.Socket; /** * @ClassName Client * @Description * @Author asus * @Date 2021/8/3 23:13 * @Version 1.0 **/ public class Client { public static void main(String[] args) { try ( //jdk1.7后新的资源释放方式 InputStream fis = new FileInputStream("E:\\文件\\data.txt"); ) { //1.请求与服务端的Socket链接 Socket socket = new Socket("127.0.0.1", 8888); //2.把字节输出流包装成一个数据输出流 DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); //3.先发送上传文件的后缀给服务端 dos.writeUTF(".txt"); //4.把文件数据发送给服务端进行数据接收 //定义一个缓冲流 byte[] buffer = new byte[1024]; int len; while ((len = fis.read(buffer)) > 0) { dos.write(buffer, 0, len); } dos.flush(); //通知服务端这边的数据发送完毕了 socket.shutdownOutput(); } catch (Exception e) { e.printStackTrace(); } } } ## 4.2 服务端 ## 这里我们跟先前的一样,我们通过多线程的方式,有一个客户端,我们就创建一个线程。 package file; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * @ClassName Server * @Description 接收客户端任意类型文件,并保存到服务端磁盘 * @Author asus * @Date 2021/8/4 8:22 * @Version 1.0 **/ 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 (IOException e) { e.printStackTrace(); } } } ## 4.3 服务端封装类 ## 在线程类里面我们来进行文件的接收,需要注意的是,客户端用的`DataOutputStream`,服务端这里要对应用`DataInputStream`来进行接收。 package file; import bio_three.ServerThreadReader; import java.io.*; import java.net.Socket; import java.util.UUID; /** * @ClassName ServerReaderThread * @Description * @Author asus * @Date 2021/8/4 8:32 * @Version 1.0 **/ public class ServerReaderThread extends Thread { private Socket socket; public ServerReaderThread(Socket socket) { this.socket = socket; } @Override public void run() { try { //1.得到一个数据输入流来读取客户端发送的数据 DataInputStream dis = new DataInputStream(socket.getInputStream()); //2.读取客户端发过来的文件类型 String suffix = dis.readUTF(); System.out.println("服务端已经成功接收到文件类型:" + suffix); //3.定义一个字节输出管道负责把客户端发送过来的文件写出去 OutputStream os = new FileOutputStream("C:\\Users\\asus\\Desktop\\file\\" + 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 (IOException e) { e.printStackTrace(); } } } # 5 java BIO模式下的端口转发思想 # 比如我们这里要给某一个用户发送消息,并不是我们发送到服务器就没事了,而是服务器在转发给另一个客户端进行数据的接收。 实现原理:服务端接收多个客户端socket的连接,记录到服务端socket集合中,多个客户端发送消息,客户端每发送一个,都要去服务端的socket集合中去记录,集合记录客户端发过来的消息,遍历集合中已有的socket,来进行对指定用户消息的转发。 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aQB6dD1D-1628077251065)(../images/image-20210804095253698.png)\]][img-aQB6dD1D-1628077251065_.._images_image-20210804095253698.png] ## 5.1 服务层 ## package bio_chat; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; /** * @ClassName Server * @Description 目标:BIO模式下的端口转发思想——服务端实现 * 服务端实现的要求: * 1、注册端口 * 2、接收客户端的socket连接,交给一个独立的线程来处理 * 3、把连接的socket存入到一个所谓的socket集合当中 * 4、接收客户端的信息,然后推送给当前所有在线的socket接收 * @Author asus * @Date 2021/8/4 11:17 * @Version 1.0 **/ public class Server { public static List<Socket> allSocketOnline = new ArrayList<>(); public static void main(String[] args) { try { //1.注册端口 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(); } } } ## 5.2 线程对象封装类 ## package bio_chat; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; /** * @ClassName ServerReaderThread * @Description * @Author asus * @Date 2021/8/4 14:31 * @Version 1.0 **/ public class ServerReaderThread extends Thread{ 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 (Exception e){ System.out.println("当前有人下线!"); //从当前集合中移除本socket Server.allSocketOnline.remove(socket); } } /** * 把当前客户端发来的消息推送给全部在线的socket * @param msg */ private void sendMsgToAllClient(String msg) throws Exception { for (Socket socket1:Server.allSocketOnline) { PrintStream ps =new PrintStream(socket1.getOutputStream()); ps.println(msg); ps.flush(); } } } ## 5.3 客户端 ## package bio_chat; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.InputStream; import java.net.Socket; /** 目标:实现客户端上传任意类型的文件数据给服务端保存起来。 */ public class Client { public static void main(String[] args) { try( InputStream is = new FileInputStream("E:\\文件\\java.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(); } } } class Client { public static void main(String[] args) { try( InputStream is = new FileInputStream("E:\\文件\\java.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(); } } } ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JsYWNrX0N1c3RvbWVy_size_16_color_FFFFFF_t_70 2] [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JsYWNrX0N1c3RvbWVy_size_16_color_FFFFFF_t_70]: /images/20220829/cef0fa9a435c4f2f9178869fd29179eb.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JsYWNrX0N1c3RvbWVy_size_16_color_FFFFFF_t_70 1]: /images/20220829/786d4fe86c3f422e90e9a4c7e8b71b71.png [43bc383fdcb94c67af1a2596f07a1c89.png]: /images/20220829/a7d2d2ec3e9c471f9a0a61ace235df4c.png [9df1323dbe61450bb0c441bf29e2db1e.png]: /images/20220829/22000ac27f3b4e4093ab4aba783f31d5.png [7c26ef611a894c3b89fbacfa5fbbff33.png]: /images/20220829/5aef1d1a71464c3bb814008407fbde57.png [a6697ab6cab44be69180ace4090c6c84.png]: /images/20220829/404c70d0d3fd4df0b351daf012c97068.png [da1df5d5e2b442d18dd0c80436f042da.png]: /images/20220829/fa69cc52ae9944eda2dee6ca9a109e69.png [c929bd15081d449dbce667fbfdc50a42.png]: /images/20220829/b2c9f38bc0364cac85919ec22048ced9.png [33ec3845b4c54677a39bdff05d44c44b.png]: /images/20220829/26b0329c0d1b4efea2c1daf89e7ff7bc.png [img-u4oyOfql-1628077251062_.._images_image-20210803210019362.png]: /images/20220829/78bf8730bcf249a78cf63725065de7b6.png [img-aQB6dD1D-1628077251065_.._images_image-20210804095253698.png]: /images/20220829/c3352ecf541e40b08c37c290308e6431.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JsYWNrX0N1c3RvbWVy_size_16_color_FFFFFF_t_70 2]: /images/20220829/cf1a3a2c8005496fbdfc0ec207304092.png
相关 一文打穿JavaBIO 努力写更多优质文章,欢迎关注[CC\_且听风吟][CC]~ ![image-20201007181807709][] 文章目录 Java BIO 矫情吗;*/ 2022年12月12日 13:59/ 0 赞/ 153 阅读
还没有评论,来说两句吧...