关于在网络中使用BIO、NIO、AIO的示例
BIO 概述与示例
- 传统的单通道直连I/O,简单,但是阻塞成本高,主要阻塞点在:
- 建立连接:new Socket()、server.accept()
- 读:in.read()
- 写:out.write()
- 特征:不管Thread是否有空,都需要为每个Socket的连接、读、写操作进行等待
- BIO 服务端示例 (ServerDemo.java)
import com.skey.demo.util.IOUtils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; /** * Description: BIO - 服务端示例 * <br/> * Date: 2019/12/26 11:45 * * @author ALion */ public class ServerDemo { public static void main(String[] args) { ServerSocket server = null; try { server = new ServerSocket(); server.bind(new InetSocketAddress(23333)); // BIO 每一个socket连接都会开启一个Thread while (true) { // 阻塞 Socket socket = server.accept(); // 启动Thread处理数据 new Thread(() -> handle(socket)).start(); } } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.close(server); } } private static void handle(Socket socket) { // 单通道: 此处先读,再写 InputStream in = null; OutputStream out = null; try { // 读取数据 in = socket.getInputStream(); byte[] buffer = new byte[1024]; // read -> 阻塞式读取(如果Client没写完,会阻塞) while (in.read(buffer) != -1) { System.out.println(new String(buffer)); } // 写出数据 out = socket.getOutputStream(); // write -> 阻塞式写出(如果Client没读完,会阻塞) out.write("World".getBytes()); out.flush(); } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.close(out, in); } } }
- BIO 客户端示例 (ClientDemo.java)
import com.skey.demo.util.IOUtils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; /** * Description: BIO - 客户端示例 * <br/> * Date: 2019/12/26 12:06 * * @author ALion */ public class ClientDemo { public static void main(String[] args) { Socket socket = null; OutputStream out = null; InputStream in = null; try { // 与Server建立连接 socket = new Socket("127.0.0.1", 23333); // 发送数据 out = socket.getOutputStream(); // write -> 阻塞式写出(如果Server没读完,会阻塞) out.write("Hello".getBytes()); out.flush(); // 表明已发送完数据,否则Server的InputStream调用read会一直阻塞 socket.shutdownOutput(); // 接收响应 in = socket.getInputStream(); byte[] buffer = new byte[1024]; // read -> 阻塞式读取(如果Server没写完,会阻塞) while (in.read(buffer) != -1) { System.out.println(new String(buffer)); } } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.close(in, out, socket); } } }
NIO 概述与示例
- NIO即non-blocking IO 或者 new IO,是对于传统BIO几个阻塞点的优化,主要逻辑是:
- 通过Selector轮询管理channel的连接、读、写状态
- 再由其他线程根据channel的状态进行不同的处理
- 特征:单独的Thread管理Socket的连接、读、写状态,其他Thread根据状态进行处理
- NIO 服务端示例 (ServerDemo.java)
import com.skey.demo.util.IOUtils; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * Description: NIO - 服务端示例 * <br/> * Date: 2019/12/26 12:48 * * @author ALion */ public class ServerDemo { public static void main(String[] args) { ServerSocketChannel ssc = null; try { // 构建 ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress(23333)); ssc.configureBlocking(false); // 设置非阻塞 // 创建用于接收连接的Selector Selector selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); // 轮询select,看是否有数据 while (selector.select() > 0) { // 遍历 keys Set<SelectionKey> keySet = selector.selectedKeys(); Iterator<SelectionKey> iter = keySet.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); // 处理 key handle(key); // 移除key,防止后续重复处理 iter.remove(); } } } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.close(ssc); } } /** * 根据key的状态进行处理 */ private static void handle(SelectionKey key) { if (key.isAcceptable()) { // 如果是可接收的 System.out.println("handle: key.isAcceptable()"); // 获取Channel ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); try { SocketChannel channel = ssc.accept(); channel.configureBlocking(false); // 设置为非阻塞 // 注册,标识channel为可读状态,并为key生成一个buffer channel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(1024)); System.out.println("channel -> OP_READ"); } catch (IOException e) { e.printStackTrace(); } } else if (key.isReadable()) { // 如果是可读的 System.out.println("handle: key.isReadable()"); // 取消READ事件 key.interestOps(key.interestOps() & ~SelectionKey.OP_READ); // 交给线程池处理 PoolEngine pool = PoolEngine.getInstance(); pool.execute(new ServerHandler(key)); } } }
- NIO 服务端数据处理类 (ServerHandler.java)
import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; /** * Description: Server 数据处理 * <br/> * Date: 2019/12/26 16:56 * * @author ALion */ public class ServerHandler implements Runnable { private SelectionKey key; public ServerHandler(SelectionKey key) { this.key = key; } @Override public void run() { try { // 获取Channel SocketChannel channel = (SocketChannel) key.channel(); // 获取为当前key生成的buffer ByteBuffer buffer = (ByteBuffer) key.attachment(); // 读取数据 int len = 0; while ((len = channel.read(buffer)) > 0) { buffer.flip(); System.out.println("msg = " + new String(buffer.array(), 0, len)); buffer.clear(); } if (len == -1) { channel.close(); System.out.println("channel -> close"); } else { // 添加READ事件 key.interestOps(key.interestOps() | SelectionKey.OP_READ); // key.selector().wakeup(); } } catch (IOException e) { e.printStackTrace(); } } }
- NIO 客户端示例 (ClientDemo.java)
import com.skey.demo.util.IOUtils; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; /** * Description: NIO - 客户端示例 * <br/> * Date: 2019/12/26 14:07 * * @author ALion */ public class ClientDemo { public static void main(String[] args) { InetSocketAddress address = new InetSocketAddress("127.0.0.1", 23333); SocketChannel channel = null; try { channel = SocketChannel.open(address); channel.configureBlocking(false); // 设置为非阻塞 // 发送消息 ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put("hello world".getBytes()); buffer.flip(); channel.write(buffer); buffer.clear(); channel.shutdownOutput(); } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.close(channel); } } }
AIO 概述与示例
- AIO即异步IO,或叫做NIO2,通过系统底层进行优化,相对于NIO其关键点在于:
- 通过监听系统底层IO连接触发后续处理,不再使用Selector轮询
- Linux底层因为是epoll,操作和Selector相似,所以AIO在Linux系统上性能相差不大
- 特征:监控系统底层IO连接,再由Thread进行处理
- AIO 服务端示例 (ServerDemo.java)
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; /** * Description: AIO - 服务端示例 * <br/> * Date: 2019/12/26 21:27 * * @author ALion */ public class ServerDemo { public static void main(String[] args) { try { AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withCachedThreadPool( Executors.newCachedThreadPool(), 4 ); AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(channelGroup); server.bind(new InetSocketAddress(23333)); CountDownLatch latch = new CountDownLatch(100); // 操作系统会回调此处 server.accept( null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(AsynchronousSocketChannel channel, Object attachment) { // server.accept(null, this); handle(channel); // 每成功处理一次,锁减1 latch.countDown(); } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); } }); try { // 防止主线程直接结束 // 阻塞,直到完成指定次数的server.accept回调 latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } } private static void handle(AsynchronousSocketChannel channel) { ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read( buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer attachment) { attachment.flip(); System.out.println(new String(attachment.array(), 0, result)); // 向客户端发送消息 channel.write(ByteBuffer.wrap("Server: world".getBytes())); } @Override public void failed(Throwable exc, ByteBuffer attachment) { } } ); } }
- AIO 客户示例 (ClientDemo.java)
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.ExecutionException; /** * Description: AIO - 客户端示例 * <br/> * Date: 2019/12/26 23:06 * * @author ALion */ public class ClientDemo { public static void main(String[] args) { AsynchronousSocketChannel channel; try { channel = AsynchronousSocketChannel.open(); ByteBuffer buffer = ByteBuffer.allocate(1024); channel.connect( new InetSocketAddress("127.0.0.1", 23333), buffer, // 当连接创建成功或失败时会调用 new CompletionHandler<Void, ByteBuffer>() { @Override public void completed(Void result, ByteBuffer attachment) { System.out.println("----connection is successfully----"); try { // 向Server发送消息 channel.write(ByteBuffer.wrap("Client: hello".getBytes())).get(); // 读取Server传回来的消息 channel.read(attachment).get(); attachment.flip(); System.out.println(new String(attachment.array())); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, ByteBuffer attachment) { } } ); // 防止主线程直接结束 Thread.sleep(10000); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } }
附:代码相关工具类
- IOUtils.java
public class IOUtils { public static void close(AutoCloseable... closeables) { for (AutoCloseable closeable : closeables) { if (closeable != null) { try { closeable.close(); } catch (Exception e) { e.printStackTrace(); } } } } }
- PoolEngine.java
import java.util.concurrent.*; public class PoolEngine { private PoolEngine() { } public static PoolEngine getInstance() { return Inner.instance; } private static class Inner { private static final PoolEngine instance = new PoolEngine(); } private static int corePoolSize = 4; private static int maximumPoolSize = 8; private static long keepAliveTime = 100; private static int queueCapacity = 1024; private ExecutorService pool = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueCapacity), new ThreadPoolExecutor.AbortPolicy()); public void execute(Runnable runnable) { if (pool != null) { pool.execute(runnable); } } public <T> Future<T> submit(Callable<T> callable) { if (pool != null) { return pool.submit(callable); } else { return null; } } public void shutdown() { if (pool != null) { pool.shutdown(); pool = null; } } }