`

基于事件的NIO多线程服务器

 
阅读更多

JDK1.4的NIO有效解决了原有流式IO存在的线程开销的问题,在NIO中使用多线程,主要目的已不是为了应对每个客户端请求而分配独立的服务线程,而是通过多线程充分使用用多个CPU的处理能力和处理中的等待时间,达到提高服务能力的目的。

AD:

JDK1.4的NIO有效解决了原有流式IO存在的线程开销的问题,在NIO中使用多线程,主要目的已不是为了应对每个客户端请求而分配独立的服务线程,而是通过多线程充分使用用多个CPU的处理能力和处理中的等待时间,达到提高服务能力的目的。

线程模型

NIO的选择器采用了多路复用(Multiplexing)技术,可在一个选择器上处理多个套接字,通过获取读写通道来进行IO操作。由于网络带宽 等原因,在通道的读、写操作中是容易出现等待的,所以在读、写操作中引入多线程,对性能提高明显,而且可以提高客户端的感知服务质量。所以本文的模型将主 要通过使用读、写线程池来提高与客户端的数据交换能力。

同时整个服务端的流程处理,建立于事件机制上。在 [接受连接->读->业务处理->写 ->关闭连接 ]这个过程中,触发器将触发相应事件,由事件处理器对相应事件分别响应,完成服务器端的业务处理。
下面我们就来详细看一下这个模型的各个组成部分。

相关事件定义 在这个模型中,我们定义了一些基本的事件:

(1)onAccept:

当服务端收到客户端连接请求时,触发该事件。通过该事件我们可以知道有新的客户端呼入。该事件可用来控制服务端的负载。例如,服务器可设定同时只为一定数量客户端提供服务,当同时请求数超出数量时,可在响应该事件时直接抛出异常,以拒绝新的连接。

(2)onAccepted:

当客户端请求被服务器接受后触发该事件。该事件表明一个新的客户端与服务器正式建立连接。

(3)onRead:

当客户端发来数据,并已被服务器控制线程正确读取时,触发该事件。该事件通知各事件处理器可以对客户端发来的数据进行实际处理了。需要注意的是,在 本模型中,客户端的数据读取是由控制线程交由读线程完成的,事件处理器不需要在该事件中进行专门的读操作,而只需将控制线程传来的数据进行直接处理即可。

(4)onWrite:

当客户端可以开始接受服务端发送数据时触发该事件,通过该事件,我们可以向客户端发送回应数据。在本模型中,事件处理器只需要在该事件中设置 。

(5)onClosed:

当客户端与服务器断开连接时触发该事件。

(6)onError:

当客户端与服务器从连接开始到最后断开连接期间发生错误时触发该事件。通过该事件我们可以知道有什么错误发生。

事件回调机制的实现

在这个模型中,事件采用广播方式,也就是所有在册的事件处理器都能获得事件通知。这样可以将不同性质的业务处理,分别用不同的处理器实现,使每个处理器的业务功能尽可能单一。

如下图:整个事件模型由监听器、事件适配器、事件触发器、事件处理器组成。

(事件模型)

1.监听器(Serverlistener):

这是一个事件接口,定义需监听的服务器事件,如果您需要定义更多的事件,可在这里进行扩展。

  1. public   interface  Serverlistener  
  2. public   void  onError(String error); 
  3. public   void  onAccept()  throws  Exception; 
  4. public   void  onAccepted(Request request)  throws  Exception; 
  5. public   void  onRead(Request request)  throws  Exception; 
  6. public   void  onWrite(Request request, Response response)  throws  Exception; 
  7. public   void  onClosed(Request request)  throws  Exception; 

2. 事件适配器(EventAdapter):

对Serverlistener接口实现一个适配器(EventAdapter),这样的好处是最终的事件处理器可以只处理所关心的事件。

  1. public   abstract   class  EventAdapter  
  2. implements  Serverlistener  
  3. public  EventAdapter() {} 
  4. public   void  onError(String error) {} 
  5. public   void  onAccept()  throws  Exception {} 
  6. public   void  onAccepted(Request request)  throws  Exception {} 
  7. public   void  onRead(Request request)  throws  Exception {} 
  8. public   void  onWrite(Request request, Response response)  throws  Exception {} 
  9. public   void  onClosed(Request request)  throws  Exception {} 

3. 事件触发器(Notifier):

用于在适当的时候通过触发服务器事件,通知在册的事件处理器对事件做出响应。触发器以Singleton模式实现,统一控制整个服务器端的事件,避免造成混乱。

  1. public   class  Notifier  
  2. private   static  Arraylist listeners =  null
  3. private   static  Notifier instance =  null
  4.  
  5. private  Notifier()  
  6. listeners = new  Arraylist(); 
  7.  
  8. /**  
  9. * 获取事件触发器  
  10. * @return 返回事件触发器  
  11. */  
  12. public   static   synchronized  Notifier  
  13. getNotifier()  
  14. if  (instance ==  null )  
  15. instance = new  Notifier(); 
  16. return  instance; 
  17. else   
  18. return  instance; 
  19.  
  20. /**  
  21. * 添加事件监听器  
  22. * @param l 监听器  
  23. */  
  24. public   void  addlistener(Serverlistener l) 
  25. synchronized  (listeners) 
  26. if  (!listeners.contains(l)) 
  27. listeners.add(l); 
  28.  
  29. public   void  fireOnAccept()  
  30. throws  Exception  
  31. for  ( int  i = listeners.size() -  1 ;  
  32. i >= 0 ; i--) 
  33. ( (Serverlistener) listeners. 
  34. get(i)).onAccept(); 
  35. // other fire method  

4. 事件处理器(Handler):

继承事件适配器,对感兴趣的事件进行响应处理,实现业务处理。以下是一个简单的事件处理器实现,它响应onRead事件,在终端打印出从客户端读取的数据。

  1. public   class  ServerHandler  
  2. extends  EventAdapter  
  3. public  ServerHandler() {} 
  4.  
  5. public   void  onRead(Request request)  
  6. throws  Exception  
  7. System.out.println("Received: "  + 
  8. new  String(data)); 

5. 事件处理器的注册。

为了能让事件处理器获得服务线程的事件通知,事件处理器需在触发器中注册。

  1. ServerHandler handler =  new  ServerHandler(); 
  2. Notifier.addlistener(handler); 

实现NIO多线程服务器

NIO多线程服务器主要由主控服务线程、读线程和写线程组成。

1. 主控服务线程(Server):

主控线程将创建读、写线程池,实现监听、接受客户端请求,同时将读、写通道提交由相应的读线程(Reader)和写服务线程(Writer),由读写线程分别完成对客户端数据的读取和对客户端的回应操作。

  1. public   class  Server  implements  Runnable 
  2. private   static  List wpool =  new  LinkedList();  
  3. private   static  Selector selector; 
  4. private  ServerSocketChannel sschannel; 
  5. private  InetSocketAddress address; 
  6. protected  Notifier notifier; 
  7. private   int  port; 
  8. private   static   int  MAX_THREADS =  4
  9.  
  10. /**  
  11. * Creat the main thread  
  12. * @param port server port  
  13. * @throws java.lang.Exception  
  14. */  
  15. public  Server( int  port)  throws  Exception 
  16. this .port = port; 
  17.  
  18. // event dispatcher  
  19. notifier = Notifier.getNotifier(); 
  20.  
  21. // create the thread pool for reading and writing  
  22. for  ( int  i =  0 ; i < MAX_THREADS; i++) 
  23. Thread r = new  Reader(); 
  24. Thread w = new  Writer(); 
  25. r.start(); 
  26. w.start(); 
  27.  
  28. // create nonblocking socket  
  29. selector = Selector.open(); 
  30. sschannel = ServerSocketChannel.open(); 
  31. sschannel.configureBlocking(false ); 
  32. address = new  InetSocketAddress(port); 
  33. ServerSocket ss = sschannel.socket(); 
  34. ss.bind(address); 
  35. sschannel.register(selector, SelectionKey.OP_ACCEPT); 
  36.  
  37. public   void  run() 
  38. System.out.println("Server started " ); 
  39. System.out.println("Server listening on port: "  + port); 
  40.  
  41. while  ( true
  42. try  
  43. int  num =  0
  44. num = selector.select(); 
  45.  
  46. if  (num >  0
  47. Set selectedKeys = selector.selectedKeys(); 
  48. Iterator it = selectedKeys.iterator(); 
  49. while  (it.hasNext()) 
  50. SelectionKey key = (SelectionKey) it.next(); 
  51. it.remove(); 
  52.  
  53. if  ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) 
  54. // Accept the new connection  
  55. ServerSocketChannel ssc =  
  56. (ServerSocketChannel) key.channel(); 
  57. notifier.fireOnAccept(); 
  58.  
  59. SocketChannel sc = ssc.accept(); 
  60. sc.configureBlocking(false ); 
  61.  
  62. Request request = new  Request(sc); 
  63. notifier.fireOnAccepted(request); 
  64.  
  65. sc.register(selector, SelectionKey.OP_READ,request); 
  66. }  
  67. else   if  ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) 
  68. Reader.processRequest(key);  
  69. key.cancel(); 
  70. }  
  71. else   if  ((key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) 
  72. Writer.processRequest(key); 
  73. key.cancel(); 
  74. }  
  75. //this selector's wakeup method is invoked  
  76. else  
  77. //register new channel for writing to selector  
  78. addRegister();  
  79. }  
  80. catch  (Exception e) 
  81. notifier.fireOnError("Error occured in Server: "  
  82. + e.getMessage()); 
  83. continue
  84.  
  85. private   void  addRegister() 
  86. synchronized  (wpool) 
  87. while  (!wpool.isEmpty()) 
  88. SelectionKey key = (SelectionKey) wpool.remove(0 ); 
  89. SocketChannel schannel = (SocketChannel) key.channel(); 
  90. try  
  91. schannel.register(selector, SelectionKey.OP_WRITE, key 
  92. .attachment()); 
  93. catch  (Exception e) 
  94. try  
  95. schannel.finishConnect(); 
  96. schannel.close(); 
  97. schannel.socket().close(); 
  98. notifier.fireOnClosed((Request) key.attachment()); 
  99. }  
  100. catch  (Exception e1) 
  101. notifier.fireOnError("Error occured in addRegister: "  
  102. + e.getMessage()); 
  103.  
  104. public   static   void  processWriteRequest(SelectionKey key) 
  105. synchronized  (wpool) 
  106. wpool.add(wpool.size(), key); 
  107. wpool.notifyAll(); 
  108. selector.wakeup();  

2. 读线程(Reader):

使用线程池技术,通过多个线程读取客户端数据,以充分利用网络数据传输的时间,提高读取效率。

  1. public   class  Reader  extends  Thread  
  2. public   void  run()  
  3. while  ( true )  
  4. try   
  5. SelectionKey key; 
  6. synchronized  (pool)  
  7. while  (pool.isEmpty())  
  8. pool.wait(); 
  9. key = (SelectionKey) pool.remove(0 ); 
  10. // 读取客户端数据,并触发onRead事件  
  11. read(key);  
  12. catch  (Exception e)  
  13. continue

3. 写线程(Writer):

和读操作一样,使用线程池,负责将服务器端的数据发送回客户端。

  1. public   final   class  Writer  extends  Thread  
  2. public   void  run()  
  3. while  ( true )  
  4. try   
  5. SelectionKey key; 
  6. synchronized  (pool)  
  7. while  (pool.isEmpty())  
  8. pool.wait(); 
  9. key = (SelectionKey) pool.remove(0 ); 
  10.  
  11. write(key);  
  12. catch  (Exception e)  
  13. continue

具体应用

NIO多线程模型的实现告一段落,现在我们可以暂且将NIO的各个API和烦琐的调用方法抛于脑后,专心于我们的实际应用中。

我们用一个简单的TimeServer(时间查询服务器)来看看该模型能带来多么简洁的开发方式。

在这个TimeServer中,将提供两种语言(中文、英文)的时间查询服务。我们将读取客户端的查询命令(GB/EN),并回应相应语言格式的当 前时间。在应答客户的请求的同时,服务器将进行日志记录。做为示例,对日志记录,我们只是简单地将客户端的访问时间和IP地址输出到服务器的终端上。

1. 实现时间查询服务的事件处理器(TimeHandler):

  1. public   class  TimeHandler  extends  EventAdapter  
  2. public  TimeHandler() {} 
  3.  
  4. public   void  onWrite(Request request, Response response)  throws  Exception  
  5. String command = new  String(request.getDataInput()); 
  6. String time = null
  7. Date date = new  Date(); 
  8.  
  9. if  (command.equals( "GB" ))  
  10. DateFormat cnDate = DateFormat.getDateTimeInstance(DateFormat.FulL, 
  11. DateFormat.FulL, Locale.CHINA); 
  12. time = cnDate.format(date); 
  13. else   
  14. DateFormat enDate = DateFormat.getDateTimeInstance(DateFormat.FulL, 
  15. DateFormat.FulL, Locale.US); 
  16. time = enDate.format(date); 
  17.  
  18. response.send(time.getBytes()); 

2. 实现日志记录服务的事件处理器(LogHandler):

  1. public   class  LogHandler  extends  EventAdapter  
  2. public  LogHandler() {} 
  3.  
  4. public   void  onClosed(Request request)  
  5. throws  Exception  
  6. String log = new  Date().toString() +  " from "  + request.getAddress().toString(); 
  7. System.out.println(log); 
  8.  
  9. public   void  onError(String error)  
  10. System.out.println("Error: "  + error); 

3. 启动程序:

  1. public   class  Start  
  2.  
  3. public   static   void  main(String[] args)  
  4. try   
  5. LogHandler loger = new  LogHandler(); 
  6. TimeHandler timer = new  TimeHandler(); 
  7. Notifier notifier = Notifier.getNotifier(); 
  8. notifier.addlistener(loger); 
  9. notifier.addlistener(timer); 
  10.  
  11. System.out.println("Server starting " ); 
  12. Server server = new  Server( 5100 ); 
  13. Thread tServer = new  Thread(server); 
  14. tServer.start(); 
  15. catch  (Exception e)  
  16. System.out.println("Server error: "  + e.getMessage()); 
  17. System.exit(-1 ); 

小  结

通过例子我们可以看到,基于事件回调的NIO多线程服务器模型,提供了清晰直观的实现方式,可让开发者从NIO及多线程的技术细节中摆脱出来,集中精力关注具体的业务实现。

原文链接:http://www.cnblogs.com/longb/archive/2006/04/04/366800.html

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics