BIO

当用户进程发起请求时,一直阻塞直到数据拷贝到用户空间为止才返回。

  1. import java.io.BufferedReader;
  2. import java.io.IOException;
  3. import java.io.InputStreamReader;
  4. import java.net.ServerSocket;
  5. import java.net.Socket;
  6. public class BIOEchoServer {
  7. public static void main(String[] args) throws IOException {
  8. ServerSocket serverSocket = new ServerSocket(8080);
  9. while (true) {
  10. Socket socket = serverSocket.accept();
  11. System.out.println("new client conn: " + socket);
  12. new Thread(() -> {
  13. try {
  14. BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  15. String msg;
  16. while ((msg = reader.readLine()) != null) {
  17. System.out.println("receive client msg: " + msg);
  18. }
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. }
  22. }).start();
  23. }
  24. }
  25. }

每来一个客户端连接都要分配一个线程,如果客户端一直增加,服务端线程会无限增加,直到服务器资源耗尽。

NIO

Java 中的 NIO 使用的是 IO 多路复用技术实现的,多个 IO 操作共同使用一个 selector(选择器)去询问哪些 IO 准备好了,selector 负责通知那些数据准备好了的 IO,它们再自己去请求内核数据。

  1. public class NIOEchoServer {
  2. public static void main(String[] args) throws IOException {
  3. // 创建一个Selector
  4. Selector selector = Selector.open();
  5. // 创建ServerSocketChannel
  6. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  7. // 绑定8080端口
  8. serverSocketChannel.bind(new InetSocketAddress(8002));
  9. // 设置为非阻塞模式
  10. serverSocketChannel.configureBlocking(false);
  11. // 将Channel注册到selector上,并注册Accept事件
  12. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  13. System.out.println("server start");
  14. while (true) {
  15. // 阻塞在select上(第一阶段阻塞)
  16. selector.select();
  17. // 如果使用的是select(timeout)或selectNow()需要判断返回值是否大于0
  18. // 有就绪的Channel
  19. Set<SelectionKey> selectionKeys = selector.selectedKeys();
  20. // 遍历selectKeys
  21. Iterator<SelectionKey> iterator = selectionKeys.iterator();
  22. while (iterator.hasNext()) {
  23. SelectionKey selectionKey = iterator.next();
  24. // 如果是accept事件
  25. if (selectionKey.isAcceptable()) {
  26. // 强制转换为ServerSocketChannel
  27. ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
  28. SocketChannel socketChannel = ssc.accept();
  29. System.out.println("accept new conn: " + socketChannel.getRemoteAddress());
  30. socketChannel.configureBlocking(false);
  31. // 将SocketChannel注册到Selector上,并注册读事件
  32. socketChannel.register(selector, SelectionKey.OP_READ);
  33. } else if (selectionKey.isReadable()) {
  34. // 如果是读取事件
  35. // 强制转换为SocketChannel
  36. SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
  37. // 创建Buffer用于读取数据
  38. ByteBuffer buffer = ByteBuffer.allocate(1024);
  39. // 将数据读入到buffer中(第二阶段阻塞)
  40. int length = socketChannel.read(buffer);
  41. if (length > 0) {
  42. buffer.flip();
  43. byte[] bytes = new byte[buffer.remaining()];
  44. // 将数据读入到byte数组中
  45. buffer.get(bytes);
  46. // 换行符会跟着消息一起传过来
  47. String content = new String(bytes, "UTF-8").replace("\r\n", "");
  48. System.out.println("receive msg: " + content);
  49. }
  50. }
  51. iterator.remove();
  52. }
  53. }
  54. }
  55. }

AIO

  1. public class AIOEchoServer {
  2. public static void main(String[] args) throws IOException {
  3. // 启动服务端
  4. AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
  5. serverSocketChannel.bind(new InetSocketAddress(8003));
  6. System.out.println("server start");
  7. // 监听accept事件,完全异步,不会阻塞
  8. serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
  9. @Override
  10. public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
  11. try {
  12. System.out.println("accept new conn: " + socketChannel.getRemoteAddress());
  13. // 再次监听accept事件
  14. serverSocketChannel.accept(null, this);
  15. // 消息的处理
  16. while (true) {
  17. ByteBuffer buffer = ByteBuffer.allocate(1024);
  18. // 将数据读入到buffer中
  19. Future<Integer> future = socketChannel.read(buffer);
  20. if (future.get() > 0) {
  21. buffer.flip();
  22. byte[] bytes = new byte[buffer.remaining()];
  23. // 将数据读入到byte数组中
  24. buffer.get(bytes);
  25. String content = new String(bytes, "UTF-8");
  26. // 换行符会当成另一条消息传过来
  27. if (content.equals("\r\n")) {
  28. continue;
  29. }
  30. System.out.println("receive msg: " + content);
  31. }
  32. }
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. @Override
  38. public void failed(Throwable exc, Object attachment) {
  39. System.out.println("failed");
  40. }
  41. });
  42. // 阻塞住主线程
  43. System.in.read();
  44. }
  45. }

阻塞与非阻塞

数据就绪前要不要等待
阻塞: 没有数据传过来时,读会阻塞直到有数据,缓冲区满时,写操作也会阻塞
非阻塞遇到这些情况,都会直接返回。

同步与异步

数据就绪后,数据操作谁完成
数据就绪后需要自己去读取就是同步,数据就绪直接读好再回调给程序是异步。

为什么Netty 仅支持NIO
image.png
为什么不建议(deprdcate)阻塞I/O(BIO/OIO)

连接数高情况下:阻塞->消耗资源,效率低