BIO NIO AIO
- by java
1、概述
BIO就是基于Thread per Request的传统server/client实现模式,
NIO通常采用Reactor模式,
AIO通常采用Proactor模式,
AIO简化了程序的编写,stream的读取和写入都有OS来完成,不需要像NIO那样子遍历Selector。Windows基于IOCP实现AIO,Linux只有epoll模拟实现了AIO。
Java7之前的JDK只支持NIO和BIO,从7开始支持AIO。
- BIO,同步阻塞式IO,简单理解:一个连接一个线程
- NIO,同步非阻塞IO,简单理解:一个请求一个线程
- AIO,异步非阻塞IO,简单理解:一个有效请求一个线程
几种通信方式:TCP/IP-BIO, TCP/IP-NIO, UDP/IP-BIO, UDP/IP-NIO, AIO。
2、几种通信方式的介绍
2.1、TCP/IP-BIO
Socket和ServerSocket实现,ServerSocket实现Server端端口监听,Socket用于建立网络IO连接。
服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。
package test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Server { private Socket socket; private ServerSocket ss; public Server() throws IOException { ss = new ServerSocket(7777); while (true) { socket = ss.accept(); BufferedReader br = new BufferedReader(new InputStreamReader(socket .getInputStream())); System.out.println("you input is : " + br.readLine()); } } public static void main(String[] args) { try { new Server(); } catch (IOException e) { e.printStackTrace(); } } }
客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。package test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class Client { Socket client; PrintWriter pw; public Client() throws UnknownHostException, IOException { client=new Socket("Socket服务器IP",7777); pw=new PrintWriter(client.getOutputStream()); BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); pw.write(br.readLine()); pw.close(); br.close(); } public static void main(String[] args) { try { new Client(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
不适用于处理多个请求 1.生成Socket会消耗过多的本地资源。2. Socket连接的建立一般比较慢。
BIO情况下,能支持的连接数有限,一般都采取accept获取Socket以后采用一个thread来处理,one connection one thread。无论连接是否有真正数据请求,都需要独占一个thread。
可以通过设立Socket池来一定程度上解决问题,
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class BIOPoolServer { ExecutorService pool = null; public BIOPoolServer(){ try { ServerSocket server = new ServerSocket(29001); pool = Executors.newFixedThreadPool(1); while(true){ pool.execute(new Handler(server.accept())); } } catch (IOException e) { e.printStackTrace(); }finally{ pool.shutdown(); } } class Handler implements Runnable{ Socket socket; public Handler(Socket socket){ this.socket = socket; } public void run() { try { BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream(),true); String msg = in.readLine(); System.out.println("The client send the msg : "+msg); out.println("The server has received!"); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { new BIOPoolServer(); } }
但是使用池需要注意的问题是:1. 竞争等待比较多。 2. 需要控制好超时时间。2.2、TCP/IP-NIO
使用Channel(SocketChannel和ServerSocketChannel)和Selector。
Server端通常由一个thread来监听connect事件,另外多个thread来监听读写事件。这样做的好处是这些连接只有在真是请求的时候才会创建thread来处理,one request one thread。这种方式在server端需要支持大量连接但这些连接同时发送请求的峰值不会很多的时候十分有效。
server端:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; import java.util.Iterator; import java.util.Set; public class NIOServer { ServerSocketChannel channel = null; public NIOServer(){ try { openChannel(); waitForConnection(); } catch (IOException e) { e.printStackTrace(); } } private void openChannel() throws IOException{ channel = ServerSocketChannel.open(); //绑定监听端口 channel.socket().bind(new InetSocketAddress(29000)); //设置为非阻塞形式 channel.configureBlocking(false); } private void waitForConnection() throws IOException{ Selector acceptSelector = SelectorProvider.provider().openSelector(); channel.register(acceptSelector, SelectionKey.OP_ACCEPT); int keyAdded = 0; while((keyAdded=acceptSelector.select())>0){ // 某客户已经准备好可以进行I/O操作了,获取其ready键集合 Set readKeys = acceptSelector.selectedKeys(); Iterator iter = readKeys.iterator(); while(iter.hasNext()){ SelectionKey sk = (SelectionKey)iter.next(); iter.remove(); if(sk.isAcceptable()){ ServerSocketChannel server = (ServerSocketChannel) sk.channel(); SocketChannel socket = server.accept(); ByteBuffer _buffer = ByteBuffer.allocate(8); IntBuffer _intBuffer = _buffer.asIntBuffer(); _buffer.clear(); socket.read(_buffer); int result = _intBuffer.get(0) + _intBuffer.get(1); _buffer.flip(); _buffer.clear(); _intBuffer.put(0, result); socket.write(_buffer); } } } } public static void main(String[] args) { new NIOServer(); } }
client端:import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.channels.SocketChannel; public class NIOClient { public void start(int first, int second){ SocketChannel channel = null; try { InetSocketAddress socketAddress = new InetSocketAddress("localhost", 29000); channel = SocketChannel.open(socketAddress); channel.configureBlocking(false); ByteBuffer _buffer = ByteBuffer.allocate(8); IntBuffer _intBuffer = _buffer.asIntBuffer(); _buffer.clear(); _intBuffer.put(0, first); _intBuffer.put(1, second); channel.write(_buffer); System.out.println("发送加法请求 " + first + "+" + second); _buffer.clear(); channel.read(_buffer); int result = _intBuffer.get(0); System.out.println("运算结果:"+result); } catch (IOException e) { e.printStackTrace(); }finally { if (channel != null) { try { channel.close(); } catch (IOException e) { } } } } public static void main(String[] args) { new NIOClient().start(3, 23); } }
2.3、UDP/IP-BIO
DatagramSocket和DatagramPacket。DatagramSocket负责监听端口以及读写数据,DatagramPacket作为数据流对象进行传输。
UDP/IP是无连接的,无法进行双向通信,除非双方都成为UDP Server。
2.4、UDP/IP-NIO
通过DatagramChannel和ByteBuffer实现。DatagramChannel负责端口监听及读写。ByteBuffer负责数据流传输。
如果要将消息发送到多台机器,如果为每个目标机器都建立一个连接的话,会有很大的网络流量压力。这时候可以使用基于UDP/IP的Multicast协议传输,Java中可以通过MulticastSocket和DatagramPacket来实现。
Multicast一般多用于多台机器的状态同步,比如JGroups。SRM, URGCP都是Multicast的实现方式。eBay就采用SRM来实现将数据从主数据库同步到各个搜索节点机器。
2.5、AIO
与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
- AsynchronousSocketChannel
- AsynchronousServerSocketChannel
- AsynchronousFileChannel
- AsynchronousDatagramChannel
其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。
from:
http://blog.sina.com.cn/s/blog_6145ed810100xvqa.html
http://my.oschina.net/bluesky0leon/blog/132361