java网络编程---BIO,NIO,AIO
知识扫盲
OSI模型:开放系统互连参考模型(open systeminterconnection reference model)
为了解决网络之间的兼容性问题,帮助各个厂商生产出可兼容的网络设备,国际标准化组织ISO于1984年提出了OSI RM,OSI 参考模型很快成为计算机网络通信的基础模型。
OSI参考模型:
OSI分层 | OSI七层功能 |
---|---|
表示层 | 处理数据格式,数据加密等 |
会话层 | 建立,维护和管理会话 |
传输层 | 建立主机端到端连接 |
网络层 | 寻址和路由选择 |
数据链路层 | 提供介质访问,数据链路管理 |
物理层 | 比特流传输 |
由于OSI模型和协议比较复杂,所以并没有得到广泛的应用。
而TCP/IP(transfer control protocol/internet protocol,传输控制协议/网际协议)模型因其开放性和易用性在实践中得到了广泛的应用,TCP/IP协议栈也成为互联网的主流协议
TCP/IP协议栈:
TCP/IP | 应用描述 | 功能 |
---|---|---|
应用层 | HTTP、Telnet、FTP、TFTP | 提供应用程序网络接口 |
传输层 | TCP/UDP | 建立端到端连接 |
网络层 | IP | 选址和路由选者 |
数据链路层 | PPP、 | 提供介质访问 |
TCP的三次握手(建立连接)和四次挥手(断开连接):
建立连接(这三个报文段完成TCP连接的建立):
- 1、请求端(通常也称为客户端)发送一个SYN段表示客户期望连接服务器端口,初始序列号为a。
- 2、服务器发回序列号为b的SYN段作为响应。同时设置确认序号为客户端的序列号加1(a+1)作为对客户端的SYN报文 的确认。
- 3、客户端设置序列号为服务器端的序列号加1(b+1)作为对服务器端SYN报文段的确认。
断开连接(四次交互完成双方向的连接的关闭):
- 1、请求端(通常也称为客户端)想终止连接则发送一个FIN段,序列号设置为a。
- 2、服务器回应一个确认序号为客户端的序列号加1(a+1)的ACK确认段,作为对客户端的FIN报文的确认。
- 3、服务器端向客户端发送一个FIN终止段(设置序列号为b,确认号为a+1)。
- 4、客户端返回一个确认报文(设置序列号为b+1)作为响应。
详细参考:This link
java Socket通信
Java Socket 可实现客户端–服务器间的双向实时通信。java.net包中定义的两个类socket和ServerSocket,分别用来实现双向连接的client和server端。
Socket通信实现方法(以下代码是简单的单线程实现)
服务端代码:
// server端
public static void main(String[] args) {
try {
// 1,创建一个socket,指定绑定端口,并开启监听这个端口
ServerSocket serverSocket = new ServerSocket(port) ;
// 2,调用accept方法,等待客户端连接
Socket socket = serverSocket.accept() ;
// 3,获取输入流,读取客户端信息
// 4,获取输出流,响应客户端信息
new Thread(new ServerHandler(socket)).start();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public class ServerHandler implements Runnable {
private Socket socket ;
public ServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
/**
* 代理服务端处理客户端请求的数据
*/
try {
// 接收客户端发送的数据
InputStream is = socket.getInputStream() ;
BufferedReader br = new BufferedReader(new InputStreamReader(is) ) ;
String info = null ;
while ((info = br.readLine()) != null){
System.out.println("客户端发来信息:"+info);
}
// 响应客户端请求
OutputStream os = socket.getOutputStream() ;
PrintWriter pw = new PrintWriter(os) ;
pw.println("我是小服,收到你的请求,我响应的数据是:你是个逗比,哈哈哈。。。");
pw.flush();
socket.shutdownOutput();
br.close();
is.close();
pw.close();
os.close();
//socket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
socket = null ;
}
}
客户端代码实现:
public static void main(String[] args) {
Socket socket = null ;
try {
// 1、创建客户端Socket,指定服务器地址和端口
socket = new Socket("127.0.0.1",port) ;
// 2,获取输出流,向服务端发起信息
OutputStream os = socket.getOutputStream() ;
PrintWriter pw = new PrintWriter(os) ;
pw.println("你好,我是小客:"+Client.class.getPackage()+Client.class.toString());
pw.flush();
socket.shutdownOutput();
// 3,获取输入流,读取服务端响应信息
InputStream is = socket.getInputStream() ;
BufferedReader br = new BufferedReader(new InputStreamReader(is)) ;
String info ;
System.out.println(br.readLine());
while ((info = br.readLine())!=null){
System.out.println("接收到服务器端响应的消息是:"+info);
break;
}
pw.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(socket != null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上是一种比较古老的方式,也就是传统的BIO模式,很明显存在的问题是,当客户端数量较多时,随之也会造成服务器压力比较大,自己实现简单的通过伪异步的方式对其进行优化, 就是利用线程池,将任务放入缓冲队列中。
// server端
public static void main(String[] args) {
ServerSocket server = null;
BufferedReader in = null;
PrintWriter out = null;
try {
server = new ServerSocket(PORT);
System.out.println("server start");
Socket socket = null;
HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
while(true){
socket = server.accept();
System.out.println("server..................");
executorPool.execute(new ServerHandler(socket));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if(server != null){
try {
server.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
server = null;
}
}
HandlerExecutorPool.java
public class HandlerExecutorPool {
private ExecutorService executor;
public HandlerExecutorPool(int maxPoolSize, int queueSize){
this.executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
maxPoolSize,
120L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize));
}
public void execute(Runnable task){
this.executor.execute(task);
}
}
BIO,NIO,AIO原理分析及代码实现
先来个例子理解一下概念,以银行取款为例:
同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写)。
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API)。
阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回)。
非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)。
Java对BIO、NIO、AIO的支持:
Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
在上面小节已经介绍过BIO这种阻塞通信模型了,存在的明显的问题是:
1,当客户端连接数较多时,服务端就会创建大量的线程,就会造成大量的内存开销和耗时
2,频繁的上下文切换而且大多数切换都是无意义的。
BIO这种阻塞通信模型(引入多路复用器的概念):