应用场景JAVA BIO(blocking I/O):同步并阻塞(传统阻塞型),服务器实现模型为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制完善(实现多个客户连接服务器)
BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。
BIO工作原理大致流程图如下:
- 服务器启动一个ServreSocket;客户端启动Socket对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与之通讯;客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝;如果有响应,客户端线程会等待请求结束后,在继续执行;
package com.kelecc.bio; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.*; public class BIOServer { public static void main(String[] args) { //创建一个线程池 ExecutorService executorService = Executors.newCachedThreadPool(); try { //创建ServerSocket ,监听8888端口 ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务启动"); while(true){ //监听,等待客户端链接,该方法阻塞,直到建立连接。 System.out.println("等待连接"); //main方法的主线程 System.out.println("线程信息 id = " + Thread.currentThread().getId()); System.out.println("线程名字 name="+Thread.currentThread().getName()); final Socket socket = serverSocket.accept(); System.out.println("连接到客户端"); //创建一个线程,与之通讯 executorService.execute(()->{ handler(socket); }); } } catch (IOException e) { e.printStackTrace(); } } public static void handler(Socket socket){ byte[] bytes = new byte[1024]; try { //得到输入流 InputStream inputStream = socket.getInputStream(); //循环读取客户端发送的数据 while(true){ System.out.println("线程信息 id = " + Thread.currentThread().getId()); System.out.println("线程名字 name="+Thread.currentThread().getName()); System.out.println("read......."); int read = inputStream.read(bytes); if(read != -1){ //控制台打印输出 System.out.println("输出客户端发送的数据"+new String(bytes,0,read)); }else{ //读取完毕 break; } } } catch (IOException e) { e.printStackTrace(); }finally { System.out.println("关闭客户端连接"); try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
代码运行:
只要没有客户端连接,线程都会阻塞,socket.accept 调用之前:
final Socket socket = serverSocket.accept();
使用CMD 创建一个客户端连接:使用命令测试8888端口
telnet 127.0.0.1 8888
连接成功:
控制台打印:
观察打印输出,我们发现:
主线程main , 在有客户端连接过后,又会阻塞在 客户端连接进来之前,所以这里会打印 两个等待连接;我们也可以看上面的流程图 一目了然;
serverSocket.accept();
而我们客户端连接的子线程 通过我们设计代码连接成功后,当我们没有发送数据,子线程会阻塞在 read/write这个 *** 作时;可以看上面的流程图;
int read = inputStream.read(bytes);
发送数据,在当前界面,按住 ctrl+] 键;
通过 send 发送数据;
观测输出日志;
发送数据成功之后,子线程依旧阻塞在 read *** 作之前
接下来,我们新建第二个客户端,启动第二个cmd,观察输出日志;
总结:每个请求都需要创建独立的线程,与对应的客户端进行数据Read,业务处理,数据Write;当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大;连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在Read *** 作上,造成线程资源浪费;
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)