背景知识点我

1. BIO

JDK5之前, JDK的IO模式只有BIO(同步阻塞)
问题: 因为阻塞的存在, 需对每个请求开启一个线程. 过多的线程切换影响操作系统性能
解决: 使用线程池, 处理不过来的放入队列, 再处理不过来的会触发其他机制
问题: 超过线程池数量的请求需要等待

  1. public class Client {
  2.  
  3. final static String ADDRESS = "127.0.0.1";
  4. final static int PORT = 8765;
  5.  
  6. public static void main(String[] args) throws IOException {
  7. Socket socket = null;
  8. BufferedReader in = null;
  9. PrintWriter out = null;
  10. try {
  11. socket = new Socket(ADDRESS, PORT);
  12. in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  13. out = new PrintWriter(socket.getOutputStream(), true); // true自动flush
  14. //向服务器端发送数据
  15. out.println("来自客户端的请求");
  16. //从服务端接收数据
  17. String response = in.readLine(); // 阻塞
  18. System.out.println("Client获取数据: " + response);
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. } finally {
  22. out.close();
  23. in.close();
  24. socket.close();
  25. }
  26. }
  27. }

服务端1: 一个请求~一个线程

  1. public class Server {
  2. final static int PROT = 8765;
  3. public static void main(String[] args) throws IOException {
  4. ServerSocket server = null;
  5. try {
  6. server = new ServerSocket(PROT);
  7. System.out.println("server start");
  8. while(true){
  9. Socket socket = server.accept(); //监听 阻塞 , socket底层会新建线程处理与客户端的三次握手
  10. //建立线程处理获取的 socket
  11. new Thread(new ServerHandler(socket)).start();
  12. }
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. } finally {
  16. server.close();
  17. }
  18. }
  19. }
  20.  
  21. class ServerHandler implements Runnable {
  22. private Socket socket;
  23. public ServerHandler(Socket socket) {
  24. this.socket = socket;
  25. }
  26.  
  27. @Override
  28. public void run() {
  29. BufferedReader in = null;
  30. PrintWriter out = null;
  31. try {
  32. in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
  33. out = new PrintWriter(this.socket.getOutputStream(), true);
  34. String body = null;
  35. while (true) {
  36. body = in.readLine(); // 阻塞
  37. if (body == null)
  38. break;
  39. System.out.println("Server获取的请求: " + body);
  40. out.println("来自服务器的响应");
  41. }
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. } finally {
  45. try {
  46. out.close();
  47. in.close();
  48. socket.close();
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. }
  54. }

服务端2: 用线程池处理请求

  1. public class Server {
  2.  
  3. final static int PORT = 8765;
  4.  
  5. public static void main(String[] args) throws IOException {
  6. ServerSocket server = null;
  7. try {
  8. server = new ServerSocket(PORT);
  9. System.out.println("server start");
  10. HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
  11. while(true){
  12. Socket socket = server.accept();
  13. executorPool.execute(new ServerHandler(socket));
  14. }
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. } finally {
  18. server.close();
  19. }
  20. }
  21. }
  22.  
  23. class HandlerExecutorPool {
  24. private ExecutorService executor;
  25. public HandlerExecutorPool(int maxPoolSize, int queueSize){
  26. this.executor = new ThreadPoolExecutor( // 带阻塞队列的线程池
  27. Runtime.getRuntime().availableProcessors(), // 初始线程数
  28. maxPoolSize, // 线程数上限 如果要处理请求的Runnable对象装满了队列, 则提高现有线程数
  29. 120L, // 如在120个时间颗粒内某线程是空闲的, 将被回收
  30. TimeUnit.SECONDS,
  31. new ArrayBlockingQueue<Runnable>(queueSize) // 存放处理请求的Runnable对象
  32. );
  33. }
  34. public void execute(Runnable task){
  35. this.executor.execute(task);
  36. }
  37. }
  38.  
  39. class ServerHandler implements Runnable {
  40. private Socket socket;
  41. public ServerHandler(Socket socket) {
  42. this.socket = socket;
  43. }
  44. @Override
  45. public void run() {
  46. BufferedReader in = null;
  47. PrintWriter out = null;
  48. try {
  49. in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
  50. out = new PrintWriter(this.socket.getOutputStream(), true);
  51. String body = null;
  52. while (true) {
  53. body = in.readLine();
  54. if (body == null)
  55. break;
  56. System.out.println("Server获取的请求: " + body); // 阻塞
  57. out.println("来自服务器的响应");
  58. }
  59. } catch (Exception e) {
  60. e.printStackTrace();
  61. } finally {
  62. try {
  63. out.close();
  64. in.close();
  65. socket.close();
  66. } catch (IOException e) {
  67. e.printStackTrace();
  68. }
  69. }
  70. }
  71. }

2.NIO1.0

JDK5以后引入了NIO1.0(多路复用机制)

伴随多路复用在程序中引入了如下概念:

Channel(通道):TCP连接的抽象,一个TCP连接对应多个Channel,这样减少TCP的连接次数。
通道与BIO中socket类似
通道与BIO中的流类似, 不过channel是双向的而流是单向的
channel有多种状态位, 能被selector识别

Buffer(缓冲区):
缓冲区是一块内存区域(数组), 在NIO中被包装成Buffer对象. Buffer提供方法用来访问该内存。
BIO中,数据存储在流中,而NIO中,数据存储在缓冲区中。
除了boolean的其他java七种基本类型都有相应的Buffer类. 最常使用的是ByteBuffer

Selector(多路复用器):负责轮询所有注册通道,根据通道状态执行相关操作。状态包括:Connect,Accept,Read,Write。
在"四种常用IO模型"里提过用select系统调用实现IO多路复用. 除select外Linux还提供了poll/epoll函数, 其中select/poll函数按顺序扫描文件句柄是否就绪,支持的文件句柄数有限; 而epoll使用基于事件驱动方式替代顺序扫描,性能更高, 对文件句柄数没有数量限制. JDK的Selector使用了epoll, 只需要一个线程轮询, 就可以接入大量的客户端.

  1. public class Client {
  2.  
  3. public static void main(String[] args) throws IOException {
  4. SocketChannel sc = null;
  5. ByteBuffer writeBuf = ByteBuffer.allocate(1024);
  6. ByteBuffer readBuf = ByteBuffer.allocate(1024);
  7. try {
  8. //创建通道
  9. sc = SocketChannel.open();
  10. //进行连接
  11. sc.connect(new InetSocketAddress("127.0.0.1", 8765));
  12. // 下面步骤可以用selector轮询代替
  13. while(true){
  14. //定义一个字节数组,然后使用系统录入功能:
  15. byte[] bytes1 = new byte[1024];
  16. System.in.read(bytes1); //阻塞
  17. //把数据放到缓冲区中
  18. writeBuf.put(bytes1);
  19. //对缓冲区进行复位
  20. writeBuf.flip();
  21. //写出数据
  22. sc.write(writeBuf);
  23. //清空缓冲区
  24. writeBuf.clear();
  25.  
  26. // 接收服务端响应
  27. sc.read(readBuf);
  28. readBuf.flip();
  29. byte[] bytes2 = new byte[readBuf.remaining()];
  30. readBuf.get(bytes2);
  31. readBuf.clear();
  32. String body = new String(bytes2);
  33. System.out.println("Client获取数据: " + body);
  34. }
  35. } catch (IOException e) {
  36. e.printStackTrace();
  37. } finally {
  38. sc.close();
  39. }
  40. }
  41. }

通过改变Selector监听Channel的状态位, 控制与客户端读写的先后顺序

  1. public class Server implements Runnable{
  2. private Selector seletor;
  3. private ByteBuffer readBuf = ByteBuffer.allocate(1024);
  4. private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
  5.  
  6. public Server(int port){
  7. try {
  8. //1 创建多路复用器selector
  9. this.seletor = Selector.open();
  10. //2 创建ServerSocket通道
  11. ServerSocketChannel ssc = ServerSocketChannel.open();
  12. //3 设置通道是否阻塞, 决定了通道了read/write/accept/connect方法是否阻塞
  13. ssc.configureBlocking(false);
  14. //4 设置通道地址
  15. ssc.bind(new InetSocketAddress(port));
  16. //5 将ServerSocket通道注册到selector上, 指定监听其accept事件
  17. ssc.register(this.seletor, SelectionKey.OP_ACCEPT);
  18. System.out.println("Server start");
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23.  
  24. @Override
  25. public void run() {
  26. while(true){
  27. try {
  28. // select阻塞, 监听相关事件
  29. this.seletor.select();
  30. // 解除阻塞, 返回选择key, key含有通道, 状态等信息
  31. Iterator<SelectionKey> keysIter = this.seletor.selectedKeys().iterator();
  32. // 进行遍历
  33. while(keysIter.hasNext()){
  34. SelectionKey key = keysIter.next();
  35. keysIter.remove();
  36. if (key.isValid()) {
  37. // 等待接收连接状态
  38. if (key.isAcceptable()) {
  39. accept(key);
  40. }
  41. // 可读状态
  42. if (key.isReadable()) {
  43. read(key);
  44. }
  45. if (key.isWritable()) {
  46. write(key);
  47. }
  48. }
  49. }
  50. } catch (IOException e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. }
  55.  
  56. private void write(SelectionKey key) {
  57. try {
  58. // 获取通道
  59. SocketChannel sc = (SocketChannel) key.channel();
  60. // 写回给客户端数据
  61. writeBuf.put("来自服务器的响应".getBytes());
  62. writeBuf.flip();
  63. sc.write(writeBuf);
  64. writeBuf.clear();
  65. // 修改监听的状态位, 如果保持OP_WRITE会导致重复写
  66. key.interestOps(SelectionKey.OP_READ);
  67. } catch (IOException e) {
  68. e.printStackTrace();
  69. }
  70. }
  71.  
  72. private void read(SelectionKey key) {
  73. try {
  74. // 获取通道
  75. SocketChannel sc = (SocketChannel) key.channel();
  76. // 读取数据, 读到buffer. 按程序运行顺序, 这里sc是否设置为阻塞效果都一样
  77. int count = sc.read(this.readBuf); // readBuf写时会改变position的值
  78. if (count == -1) {
  79. key.channel().close();
  80. key.cancel(); //取消该通道在selector的注册, 之后不会被select轮询到
  81. return;
  82. }
  83. // 有数据则进行读取. 读取前需要将position和limit进行复位
  84. readBuf.flip();
  85. // 根据缓冲区的数据长度创建相应大小的byte数组, 接收缓冲区的数据
  86. byte[] bytes = new byte[this.readBuf.remaining()];
  87. // 接收缓冲区数据
  88. readBuf.get(bytes);
  89. readBuf.clear();
  90. String body = new String(bytes).trim();
  91. System.out.println("Server获取的请求: " + body);
  92. // 如果保持OP_READ会导致重复读
  93. sc.register(this.seletor, SelectionKey.OP_WRITE);
  94. } catch (IOException e) {
  95. e.printStackTrace();
  96. }
  97. }
  98.  
  99. private void accept(SelectionKey key) {
  100. try {
  101. // 获取服务通道
  102. ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
  103. // 获取客户端通道.
  104. SocketChannel sc = ssc.accept();
  105. // 设置非阻塞模式
  106. sc.configureBlocking(false);
  107. // 将客户端通道注册到多路复用器上,指定监听事件
  108. sc.register(this.seletor, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
  109. } catch (IOException e) {
  110. e.printStackTrace();
  111. }
  112. }
  113.  
  114. public static void main(String[] args) {
  115. new Thread(new Server(8765)).start();;
  116. }
  117. }

BIO客户端与NIO服务端通信需注意的:

BIO服务端, 一次IO有明确的结束点, 客户端再次read会返回-1

NIO服务端一次IO结束后, 没有关闭通道, 它可能把通道从读状态转为写状态. 于是selector不监听读了, 客户端再次read什么都没返回, 就会阻塞.

3.NIO2.0

JDK7引入了NIO2.0(即AIO)

NIO1.0中, IO过程没有阻塞, 阻塞被转移到了Selector轮询上. Selector管理所有的Channel, 因此能把总阻塞时间缩到最短.

NIO2.0中, 供我们调用的IO API都是非阻塞的, 背后复杂的实现过程(肯定有阻塞)被转移到了JDK底层和操作系统上. 我们的程序的IO调用可以做到立即返回.

同样有Channel和Buffer, 但没有Selector

  1. public class Server {
  2. //线程池
  3. private ExecutorService executorService;
  4. //异步通道线程组
  5. private AsynchronousChannelGroup threadGroup;
  6. //服务器通道
  7. public AsynchronousServerSocketChannel assc;
  8.  
  9. public Server(int port){
  10. try {
  11. //创建一个线程池
  12. executorService = Executors.newCachedThreadPool();
  13. //使用线程池创建异步通道线程组, 该线程组在底层支持着我们的异步操作
  14. threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
  15. //使用 异步通道线程组 创建服务器通道
  16. assc = AsynchronousServerSocketChannel.open(threadGroup);
  17. //给通道绑定端口
  18. assc.bind(new InetSocketAddress(port));
  19. System.out.println("server start");
  20. // 下面的accept不会阻塞 , 一个accept只能接收一个连接请求
  21. // accept第一个参数: 被绑定到IO操作的关联对象(子类), 第二个参数 CompletionHandler<AsynchronousSocketChannel, 关联对象(父类)>, 操作成功后执行的回调句柄
  22. // 如果接受了一个新的连接, 其结果AsynchronousSocketChannel会被绑定与assc通道到相同的AsynchronousChannelGroup
  23. assc.accept(this, new ServerCompletionHandler());
  24. // 这里为了避免程序结束, 异步通道线程组结束就不会执行回调了
  25. Thread.sleep(Integer.MAX_VALUE);
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. public static void main(String[] args) {
  31. new Server(8765);
  32. }
  33.  
  34. }
  1. //第一个参数: IO操作结果; 第二个参数: 被绑定到IO操作的关联对象
  2. public class ServerCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, Server> {
  3.  
  4. // 以下两个重载参数与CompletionHander的模板参数一致, 回调时被传入IO结果和IO操作时设置的关联对象
  5. @Override
  6. public void completed(AsynchronousSocketChannel asc, Server attachment) {
  7. // 完成当前连接时, 首先, 为下一个客户端能接入再次调用accept异步方法
  8. attachment.assc.accept(attachment, this);
  9. // 其次, 执行下一步的读操作
  10. read(asc);
  11. }
  12. @Override
  13. public void failed(Throwable exc, Server attachment) {
  14. exc.printStackTrace();
  15. }
  16.  
  17. private void read(final AsynchronousSocketChannel asc) {
  18. //读取数据
  19. ByteBuffer buf = ByteBuffer.allocate(1024);
  20. // 第一个参数: 读操作的Buffer, 第二个参数: IO关联对象, 第三个参数:CompletionHandler<Integer, IO管理对象父类>
  21. asc.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() {
  22. @Override
  23. public void completed(Integer resultSize, ByteBuffer attachment) {
  24. //进行读取之后,重置标识位
  25. attachment.flip();
  26. //获得读取的字节数
  27. System.out.println("Server端" + "收到客户端的数据长度为:" + resultSize);
  28. //获取读取的数据
  29. String resultData = new String(attachment.array()).trim();
  30. System.out.println("Server端" + "收到客户端的数据信息为:" + resultData);
  31. String response = "From服务端To客户端: 于" + new Date() + "收到了请求数据"+ resultData;
  32. write(asc, response);
  33. }
  34. @Override
  35. public void failed(Throwable exc, ByteBuffer attachment) {
  36. exc.printStackTrace();
  37. }
  38. });
  39. }
  40.  
  41. private void write(AsynchronousSocketChannel asc, String response) {
  42. try {
  43. ByteBuffer buf = ByteBuffer.allocate(1024);
  44. buf.put(response.getBytes());
  45. buf.flip();
  46. // 写操作, 异步
  47. Future<Integer> future = asc.write(buf);
  48. // 阻塞等待结果
  49. future.get();
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. } catch (ExecutionException e) {
  53. e.printStackTrace();
  54. }
  55. }
  56. }
  1. public class Client {
  2.  
  3. private AsynchronousSocketChannel asc ;
  4. public Client() throws Exception {
  5. asc = AsynchronousSocketChannel.open();
  6. }
  7.  
  8. public void connect() throws InterruptedException, ExecutionException{
  9. // get()阻塞
  10. asc.connect(new InetSocketAddress("127.0.0.1", 8765)).get();
  11. }
  12.  
  13. public void write(String request){
  14. try {
  15. // get()阻塞
  16. asc.write(ByteBuffer.wrap(request.getBytes())).get();
  17. read();
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. }
  22.  
  23. private void read() throws IOException {
  24. ByteBuffer buf = ByteBuffer.allocate(1024);
  25. try {
  26. // get()阻塞
  27. asc.read(buf).get();
  28. buf.flip();
  29. byte[] respByte = new byte[buf.remaining()];
  30. buf.get(respByte);
  31. System.out.println(new String(respByte,"utf-8").trim());
  32. // 关闭
  33. asc.close();
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. } catch (ExecutionException e) {
  37. e.printStackTrace();
  38. } catch (UnsupportedEncodingException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42.  
  43. public static void main(String[] args) throws Exception {
  44. Client c1 = new Client();
  45. Client c2 = new Client();
  46. c1.connect();
  47. c2.connect();
  48.  
  49. c1.write("aa");
  50. c2.write("bbb");
  51. }
  52. }

JDK的BIO, NIO, AIO的更多相关文章

  1. (转)也谈BIO | NIO | AIO (Java版)

    原文地址: https://my.oschina.net/bluesky0leon/blog/132361 关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一 ...

  2. 也谈BIO | NIO | AIO (Java版--转)

    关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一个解释: BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...

  3. Netty5序章之BIO NIO AIO演变

    Netty5序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使 ...

  4. I/O模型系列之三:IO通信模型BIO NIO AIO

    一.传统的BIO 网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请 ...

  5. 【netty】(1)---BIO NIO AIO演变

    BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用的技术. Net ...

  6. Netty序章之BIO NIO AIO演变

    Netty序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用 ...

  7. java BIO/NIO/AIO 学习

    一.了解Unix网络编程5种I/O模型 1.1.阻塞式I/O模型 阻塞I/O(blocking I/O)模型,进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区中或者发生错误 ...

  8. 转载:BIO | NIO | AIO

    http://my.oschina.net/bluesky0leon/blog/132361 也谈BIO | NIO | AIO (Java版)   转载自:zheng-lee博客 发布时间: 201 ...

  9. BIO,NIO,AIO总结

    熟练掌握 BIO,NIO,AIO 的基本概念以及一些常见问题是你准备面试的过程中不可或缺的一部分,另外这些知识点也是你学习 Netty 的基础. BIO,NIO,AIO 总结 1. BIO (Bloc ...

随机推荐

  1. 88. Merge Sorted Array(从后向前复制)

    Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array. Note:Yo ...

  2. 【转】阿里巴巴技术专家杨晓明:基于Hadoop技术进行地理空间分析

    转自:http://www.csdn.net/article/2015-01-23/2823687-geographic-space-base-Hadoop [编者按]交通领域正产生着海量的车辆位置点 ...

  3. java内存回收

    java中引用类型 强引用 Persnon  p = new Person(); 当指向Person对象的引用计数为0时,Person对象才能被垃圾回收器回收. 软引用 SoftReference&l ...

  4. IDEA 编译报错: 未结束的字符串文字

    最近在搞新项目,同事用的eclipse开发,而我用的是ide,项目初始是由同事创建的,项目编码是UTF-8,而我开发的ide工具默认是GBK编码,导致在编译的时候报错: 未结束的字符串文字 这个问题就 ...

  5. python中统计计数的几种方法

    以下实例展示了 count() 方法的使用方法: 1 2 3 4 5 6 # !/usr/bin/python3   T = (123, 'Google', 'Runoob', 'Taobao', 1 ...

  6. Mac OS下开启自带的apache服务

    Apache路径 /etc/apache2/ [root@GGs-MacBook-Pro:/Volumes/SSD/blog#cd /etc/apache2/ [root@GGs-MacBook-Pr ...

  7. 解决“检测到有潜在危险的Request.Form值”

    先看如下 web.config 的代码: <system.web>     <compilation debug="true" targetFramework=& ...

  8. Django学习笔记之Cookie、Session和自定义分页

    cookie Cookie的由来 大家都知道HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不 ...

  9. 20144303《Java程序设计》第10周学习总结

    20144303<Java程序设计>第10周学习总结 教材学习内容总结 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程 ...

  10. 安装完kali需要做的一些事情

    1. 没有声音的问题[ kali ] 参考:http://tieba.baidu.com/p/4343219808 用pulseaudio --start会看到一些信息,提示类似root用户之类的 我 ...