比喻:餐厅

image.png
单线程NIO单线程BIO一样,它只有一个员工,这个员工有分流(Select)的能力

  1. 他负责接待新来的客人,安排客人坐下,给客人菜单(安顿客人 事件)
  2. 客人要点单了,他过去帮客人下单(帮客人下单 事件)

BIO处理的单元是用户;而NIO处理的单元是事件

  • BIO,有客人来,我去跟踪这个客人为它服务(普通函数的思想)
  • NIO,安顿客人事件发生,我触发这个事件,并处理这个事件(Reactor模式)

代码

服务端代码

  1. package NIO;
  2. //NIO的服务器(餐厅)
  3. public class NIOServer{
  4. //通道管理器(服务员)
  5. private Selector selector;
  6. //(餐厅开门)
  7. //获得一个ServerSocket通道,并对该通道做一些初始化的工作
  8. public void initServer(int port) throws IOException{
  9. //获得一个ServerSocket通道(开一扇门)
  10. ServerSocketChannel serverChannel = ServerSocketChannel.open();
  11. serverChannel.configureBlocking(false); //设置通道为非阻塞
  12. serverChannel.socket().bind(new InetSocketAddress(port)); //将该通道对应的ServerSocket绑定到port端口
  13. //获得一个通道管理器(服务员)
  14. this.selector = Selector.open();
  15. //(将此服务员安排在这个餐厅;并将餐厅的门打开)
  16. //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件
  17. //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞
  18. serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  19. }
  20. //(服务员在这里领任务:看是否有客人来;是否有人点餐;……)
  21. //采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
  22. public void listen() throws IOException{
  23. System.out.println("服务器启动成功!");
  24. //轮询访问selector
  25. while(true) {
  26. //(服务员看看有没有事情要处理
  27. // 1. 如果没事情,就会堵塞在这一句,等待事情来;
  28. // 2. 如果有事情,就继续下面的代码
  29. // )
  30. //当注册的事件到达时,方法返回;否则,该方法会一直阻塞
  31. selector.select();
  32. //(取出所有的事情,一件一件做)
  33. //获得selector中选中的项的迭代器,选中的项为注册的事件
  34. Iterator<?> ite = this.selector.selectedKeys().iterator();
  35. while(ite.hasNext()){
  36. //(服务员要处理的事情名称)
  37. SelectionKey key = (SelectionKey)ite.next();
  38. //(删除这个事情,表示已经做了)
  39. //删除已选的key,以防重复处理
  40. ite.remove();
  41. //(去处理这个事情)
  42. //处理这个key
  43. handler(key);
  44. }
  45. }
  46. }
  47. //(服务员干活)
  48. //处理请求
  49. public void handler(SelectionKey key) throws IOException{
  50. if(key.isAcceptable()) {
  51. //(有客人来)
  52. //客户端请求连接事件
  53. handlerAccept(key);
  54. } else if(key.isReadable()){
  55. //(客人要点餐)
  56. //获得可读的事件
  57. handelerRead(key);
  58. }
  59. }
  60. //(服务员如何安排客人坐下)
  61. //处理连接请求
  62. public void handlerAccept(SelectionKey key) throws IOException {
  63. ServerSocketChannel server = (ServerSocketChannel) key.channel();
  64. SocketChannel channel = server.accept(); //获得和客户端连接的通道
  65. channel.configureBlocking(false); //设置成非阻塞
  66. System.out.println("新的客户端连接");
  67. //在这里可以给客户端发送信息
  68. //...
  69. //(客户坐下以后,把菜单给他,给他点菜的权限)
  70. //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限
  71. channel.register(this.selector, SelectionKey.OP_READ);
  72. }
  73. //(服务员如何取得客人的菜单)
  74. //处理读的事件
  75. public void handelerRead(SelectionKey key) throws IOException {
  76. //服务器可读取消息:得到事件发生的Socket通道
  77. SocketChannel channel = (SocketChannel) key.channel();
  78. //创建读取缓冲区
  79. ByteBuffer buffer = ByteBuffer.allocate(1024);
  80. channel.read(buffer);
  81. byte[] data = buffer.array();
  82. String msg = new String(data).trim();
  83. System.out.println("服务端收到的信息:" + msg);
  84. ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
  85. channel.write(outBuffer); //将消息送回给客户端
  86. }
  87. //(正式开门)
  88. //启动服务端测试
  89. public static void main(String[] args) throws IOException{
  90. NIOServer server = new NIOServer();
  91. server.initServer(8000);
  92. server.listen();
  93. }
  94. }

客户端:可以使用windows的telnet

  1. # 1. 打开Windows命令行
  2. # 2. 使用telnet连接服务端
  3. > telnet 127.0.0.1 8000
  4. # 3. 给服务端发送消息
  5. > send wo yao chifan

参考链接

  1. NIO代码分析(讲的很不错,推荐)