一、I/O模型的基本概念
I/O模型的简单理解就是,用什么样的通道进行数据的发送和接受,很大程度上决定了程序通讯的性能。
JAVA程序支持3种网络通信模型/IO模式:BIO、NIO、AIO
BIO:同步并阻塞(传统阻塞模型),服务器的实现模式为一个连接一个线程,及客户端有连接请求时,服务器端需要启动一个线程进行处理,如果这个线程不做任何事情,就会造成不必要的线程开销。
适用于连接数较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4之前的唯一选择,但程序简单理解。
NIO:同步非阻塞,服务器的实现模式为,一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接由I/O请求就进行处理。
适用于连接数目多而且连接比较短的架构,比如聊天室,弹幕系统,服务器之间的通讯等,编程比较复杂,JDK1.4开始支持。
AIO(NIO.2):异步非阻塞(尚未进行广泛应用),AIO引入了异步通道的概念,采用了Proactor模式,简化了程序的编写,有效的请求才可以启动线程。特点是,先由操作系统完成后才通知服务端应用程序启动线程去进行处理
适用于连接数较多且连接时间较长的应用,比如相册服务器,编程比较复杂,JDK1.7引入。
二、BIO
主要定义如上所示。BIO可以通过线程池的机制来进行改善。
BIO编程的简单流程:
1.服务端启动一个ServerSocket
2.客户端启动一个Socket对服务器进行通讯,默认情况下服务器需要对每个客户建立一个线程与之通讯。
3.客户端发起请求后,先咨询服务器是否有线程响应,如果没有则会等待或者直接拒绝
4.如果有响应,客户端线程会等待请求结束后,再继续执行
BIO本地测试时,客户端可以通过telnet发送请求,服务端代码如下
1 package com.mytest.bio; 2 3 import java.io.InputStream; 4 import java.net.ServerSocket; 5 import java.net.Socket; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 9 public class BioServer { 10 11 public static void main(String[] args) throws Exception { 12 13 //1.创建一个线程池 14 ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); 15 16 //2.如果有客户端连接,就创建一个线程,与之通信 17 //创建ServerSocket 18 ServerSocket serverSocket = new ServerSocket(6666); 19 20 System.out.println("服务器已启动"); 21 22 while (true) { 23 //监听,等待客户端连接 24 final Socket socket = serverSocket.accept(); 25 System.out.println("连接到一个客户端"); 26 27 //创建一个线程与之通讯 28 newCachedThreadPool.execute(new Runnable() { 29 public void run() { 30 //可以和客户端进行通讯 31 handler(socket); 32 } 33 }); 34 } 35 } 36 37 //编写一个handler方法,和客户端进行通讯 38 public static void handler(Socket socket) { 39 try { 40 System.out.println("线程信息 id=" + Thread.currentThread().getId() + "名字=" + Thread.currentThread().getName()); 41 byte[] bytes = new byte[1024]; 42 //通过socket获取输入流 43 InputStream inputStream = socket.getInputStream(); 44 //循环读取客户端发送的数据 45 while (true) { 46 System.out.println("线程信息 id=" + Thread.currentThread().getId() + "名字=" + Thread.currentThread().getName()); 47 int read = inputStream.read(bytes); 48 if (read != -1) { 49 System.out.println(new String(bytes, 0, read)); 50 }else { 51 break; 52 } 53 } 54 }catch (Exception e) { 55 e.printStackTrace(); 56 }finally { 57 //无论处理结果如何,都需要关闭socket 58 System.out.println("关闭和client的连接"); 59 try{ 60 socket.close(); 61 }catch (Exception e) { 62 e.printStackTrace(); 63 } 64 } 65 } 66 }
BIO存在的问题:
1.每个请求都会创建独立的线程,与对应的客户端进行read/write
2.当并发数较大时,需要创建大量的线程来处理连接,系统资源占用较大
3.连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在read操作上,造成了线程资源的浪费。