> 文章列表 > Java BIO从入门示例到多线程示例

Java BIO从入门示例到多线程示例

Java BIO从入门示例到多线程示例

一、介绍

1、概述:

  • Java BlO就是传锤的Java IO编程,其相关的类和接口在Java.io 包中
  • BIO同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器 端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销【简单示意图 】

Java BIO从入门示例到多线程示例

2、java BIO工作机制

Java BIO从入门示例到多线程示例

3 传统的BIO编程实例回顾

  • 网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信(绑 定IP地址和端口),客户端通过连接操作向服务端监听的端口地址发起连接请求,基于TCP协议下进行三次握手连接,连接成功后,双方通过网络套接字(Socket)进行通信。
  • 传统的同步阻塞模型开发中,服务端ServerSocket负责绑定IP地址,启动监听端口;客户端Socket负责发起 连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。
  • 基于BIO模式下的通信,客户端-服务端是完全同步,完全藕合的。

二、示例

1、单线程通信

client代码:

public class BioClient {public static void main(String[] args) {OutputStream outputStream = null;BufferedWriter bw = null;Socket socket = null;try {socket = new Socket("127.0.0.1", 9999);outputStream = socket.getOutputStream();PrintStream ps = new PrintStream(outputStream);ps.println("hello , I am form china");ps.flush();} catch (IOException e) {throw new RuntimeException(e);} }
}

server代码:

public class BioServer {public static void main(String[] args) {ServerSocket serverSocket = null;Socket accept = null;InputStream inputStream = null;BufferedReader br = null;try {serverSocket = new ServerSocket(9999);accept = serverSocket.accept();inputStream = accept.getInputStream();br = new BufferedReader(new InputStreamReader(inputStream));String s = null;if ((s = br.readLine()) != null){System.out.println("服务器收到消息 ====" + s);}} catch (IOException e) {throw new RuntimeException(e);}}
}

输出:

服务器收到消息 ==== hello , I am form china

小结

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

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

client代码:

public class TwoBioClient {public static void main(String[] args) {OutputStream outputStream = null;BufferedWriter bw = null;Socket socket = null;try {socket = new Socket("127.0.0.1", 9999);outputStream = socket.getOutputStream();PrintStream ps = new PrintStream(outputStream);ps.println("hello , I am form china");ps.flush();} catch (IOException e) {throw new RuntimeException(e);}}
}

server代码:

public class TwoBioServer {public static void main(String[] args) {ServerSocket serverSocket = null;Socket accept = null;InputStream inputStream = null;BufferedReader br = null;try {serverSocket = new ServerSocket(9999);accept = serverSocket.accept();inputStream = accept.getInputStream();br = new BufferedReader(new InputStreamReader(inputStream));String s = null;if ((s = br.readLine()) != null){System.out.println("服务器收到消息 ====" + s);}} catch (IOException e) {throw new RuntimeException(e);}}
}

输出:

服务器收到消息 ==== hello , I am form china
服务器收到消息 ==== hello , I am form china
服务器收到消息 ==== hello , I am form china

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

概述

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

client代码:

public class MultiBioClient {public static void main(String[] args) {OutputStream outputStream = null;BufferedWriter bw = null;Socket socket = null;try {socket = new Socket("127.0.0.1", 9999);outputStream = socket.getOutputStream();PrintStream ps = new PrintStream(outputStream);Scanner scanner = new Scanner(System.in);while (true){System.out.print("请说:");String msg = scanner.nextLine();ps.println(msg);ps.flush();}} catch (IOException e) {throw new RuntimeException(e);} }
}

server代码:

public class MultiBioServer {public static void main(String[] args) {ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(9999);while (true){Socket socket = serverSocket.accept();new MultiServerThreadReader(socket).start();}} catch (IOException e) {throw new RuntimeException(e);}}
}

线程执行获取客户端信息并读取:

public class MultiServerThreadReader extends Thread{private Socket socket;public MultiServerThreadReader(Socket socket){this.socket = socket;}@Overridepublic void run() {try {InputStream inputStream = socket.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));String s = null;while ((s = br.readLine()) != null){System.out.println("服务器收到消息 ====" + s);}} catch (IOException e) {throw new RuntimeException(e);}}
}

Idea配置多个client启动实例

Java BIO从入门示例到多线程示例

输出:

===client1:
请说:
ppp
请说:
你在干嘛?
请说:
我是第一个client
请说:
===client2:
请说:
lll
请说:
还钱!!!
请说:
我是第二个client
请说:
===client3:
请说:
我是第三个client
请说服务器收到消息 ==== lll
服务器收到消息 ==== ppp
服务器收到消息 ==== 你在干嘛?
服务器收到消息 ==== 还钱!!!
服务器收到消息 ==== 我是第二个client
服务器收到消息 ==== 我是第一个client
服务器收到消息 ==== 我是第三个client

小结

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

4、使用线程池实现异步BIO通信

概述

  • 在上述案例中:客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。
  • 接下来我们采用一个伪异步I/O的通信框架,采用线程池和任务队列实现,当客户端接入时,将客户端的Socket封装成一个Task(该任务实现Java. lang. Runnable(线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中Socket任务进行处理,由于线程池
  • 可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

如图:

Java BIO从入门示例到多线程示例

client代码:

public class AsyncBioClient {public static void main(String[] args) {OutputStream outputStream = null;BufferedWriter bw = null;Socket socket = null;try {socket = new Socket("127.0.0.1", 9999);outputStream = socket.getOutputStream();PrintStream ps = new PrintStream(outputStream);Scanner scanner = new Scanner(System.in);while (true){System.out.print("请说:");String msg = scanner.nextLine();ps.println(msg);ps.flush();}} catch (IOException e) {throw new RuntimeException(e);} }
}

server代码:

public class AsyncBioServer {public static void main(String[] args) {ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(9999);ExecutorService executorService = new ThreadPoolExecutor(3, 6, 10, TimeUnit.SECONDS, new LinkedBlockingDeque(2), //队列大小为2new MyThreadFactory(),//这里用简单的线程工厂new RejectedExecutionHandler() {@Override//错误策略,超过最大线程数后执行该方法public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("ThreadName:" + Thread.currentThread().getName() + "执行拒绝策略:");}});while (true) {Socket socket = serverSocket.accept();executorService.execute(new ServerRunnable(socket));}} catch (IOException e) {throw new RuntimeException(e);}}public static class MyThreadFactory implements ThreadFactory {@Overridepublic Thread newThread(Runnable r) {return new Thread(r);}}}

线程执行获取客户端信息并读取:

public class ServerRunnable implements Runnable{private Socket socket;public ServerRunnable(Socket socket){this.socket = socket;}@Overridepublic void run() {try {InputStream inputStream = socket.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));String s = null;while ((s = br.readLine()) != null){System.out.println("服务器收到消息 ====" + s);}} catch (IOException e) {throw new RuntimeException(e);}}
}

输出:

===client1:
请说:
ppp
请说:
你在干嘛?
请说:
我是第一个client
请说:
===client2:
请说:
lll
请说:
还钱!!!
请说:
我是第二个client
请说:
===client3:
请说:
我是第三个client
请说服务器收到消息 ==== lll
服务器收到消息 ==== ppp
服务器收到消息 ==== 你在干嘛?
服务器收到消息 ==== 还钱!!!
服务器收到消息 ==== 我是第二个client
服务器收到消息 ==== 我是第一个client
服务器收到消息 ==== 我是第三个client

小结

  • 伪异步旧采用了线程池实现,因此避免了为每个请求创建一个独立线程造成线程资源耗尽的问题,但由于底层 依然是采用的同步阻塞模型,因此无法从根采上解决问题。
  • 如果单个消息处理的缓慢,或者服务器线程池中的全部线程都被阻塞,那么后续socket的I/O消息
    都将在队列 中排队。新的Socket请求将被拒绝,客户端会发生大量连接超时

5、基于BIO形式下的文件上传

目标

支持任意类型文件形式的上传

客户端开发

/* 目标:服务端开发,可以实现接收客户端的任意类型文件,并保存到服务器端磁盘*/
public class FileClient {public static void main(String[] args) {try (FileInputStream fis = new FileInputStream(new File("C:\\\\Users\\\\Administrator\\\\Desktop\\\\图片\\\\logo2.png"));) {//1、创建客户端Socket socket = new Socket("127.0.0.1", 9999);//2、把字节输出流包装成字节输出流DataOutputStream dos = new DataOutputStream(socket.getOutputStream());//3、发送后缀名称dos.writeUTF(".png");int len = 0;byte[] buffer = new byte[1024];//4、把文件数据发送给服务端进行接收while ((len = fis.read()) > 0) {dos.write(buffer, 0, len);}dos.flush();//5、通知服务器,文件发送完毕socket.shutdownOutput();} catch (IOException e) {throw new RuntimeException(e);}}
}

服务端开发

public class FileServer {public static void main(String[] args) {try {//1、创建服务器ServerSocket ss = new ServerSocket(9999);while (true){Socket socket = ss.accept();//2、交给一个独立的线程来处理与这个客户端的文件通信需求new FileServerThread(socket).start();}} catch (IOException e) {throw new RuntimeException(e);}}
}
public class FileServerThread extends Thread {private Socket socket;public FileServerThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {FileOutputStream fos = null;try {//1、用字节流来获取客户端发送过来的数据DataInputStream dis = new DataInputStream(socket.getInputStream());//2、得到文件后缀名称String suffix = dis.readUTF();System.out.println("服务端已经成功接收到了文件类型:" + suffix);//3、定义文件输出流,将得到的文件写出去fos = new FileOutputStream("d:\\\\pic\\\\" + UUID.randomUUID().toString() + suffix);//4、从文件输入流中读取文件数据,写出到字节输出流中byte[] buffer = new byte[1024];int len = 0;while ((len = dis.read(buffer)) > 0){fos.write(buffer, 0, len);}fos.flush();System.out.println("服务端接收文件保存成功!");} catch (IOException e) {throw new RuntimeException(e);} finally {try {fos.close();} catch (IOException e) {throw new RuntimeException(e);}}}
}

输出:

服务端已经成功接收到了文件类型:.png
服务端接收文件保存成功!

小结

  • 同步阻塞模式下(BIO),客户端怎么发,服务端就必须对应的怎么收。如客户端用的是
    DataOutputStream,那么服务端就该用DataInputStream,客户端dos.writeUTF(“.jpg”);服务端
    就该String suffix = dis.readUTF();
  • 客户端发完数据后必须通知服务端自己已经发完socket.shutdownOutput(),否则服务端会一直等
    待。

三、源码下载

https://gitee.com/charlinchenlin/store-pos