nettty

https://www.jianshu.com/p/e58674eb4c7a

1. Netty - 異步和事件驅動

1. Netty 能夠幫助搭建允許系統能夠擴展到支持150000名併發用戶。

2. Netty 設計關鍵: 異步 + 事件驅動

1.1 Java網絡編程(BIO)

典型的BIO服務端:

1. 一個主線程在某個port監聽,等待客戶端連接。

2. 當接收到客戶端發起的連接時,創建一個新的線程去處理客戶端請求。

3. 主線程重新回到port監聽,等待下一個客戶端連接。

缺點:

1. 每個新的客戶端Socket都需要創建一個新的Thread處理,將會導致大量的線程處於休眠狀態。

2. 每個線程都有調用棧的內存分配,連接數非常多時,耗費較多內存。

3. 連接數比較多時,創建大量線程,上下文切換所帶來的開銷較大。

代碼:

  1. public void serve(int port) throws IOException {
  2. // 創建Socket
  3. ServerSocket serverSocket = new ServerSocket(port);
  4. // 等待客戶端連接
  5. Socket clientSocket = serverSocket.accept();
  6. // 創建輸入流
  7. BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
  8. PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
  9. String request, response;
  10. while((request = in.readLine()) != null) {
  11. if("Done".equals(request)) {
  12. break;
  13. }
  14. response = processRequest(request);
  15. out.println(response);
  16. }
  17. }

1.2 Java NIO

1. 使用Selector來實現Java的非阻塞I/O操作。將多個Socket的讀寫狀態綁定到Selector上,允許在任何時間檢查任意的讀操作/寫操作的完成狀態。

2. 允許單個線程處理多個併發的連接。

1.3 Netty的核心組件

Netty的主要構件塊:

1. Channel

2. 回調

3. Future

4. 事件和ChannelHandler

1.3.1 Channel

Channel是傳入(入站)或者傳出(出站)數據的載體(如一個文件、一個Socket或一個硬件設備)。可以被打開或者被關閉,連接或斷開連接。

1.3.2 回調

回調只是:先寫一段代碼,該段代碼在將來某個適當的時候會被執行。Netty大量使用了回調,比如:某ChannelHandler中的channelActive()方法則是一個回調,表示連接建立時,請執行該段回調代碼。

1.3.3 Future

異步操作佔位符。在操作完成時,提供結果的訪問。

JDK提供的Future和ChannelFuture對比:

1. JDK提供的Future需要手動檢查對應的操作是否完成,或一直阻塞直到它完成

2. ChannelFuture能夠註冊Listener監聽器,監聽器的回調函數operationComplete()能異步的在操作完成時被調用。

代碼:

  1. public static void connect() {
  2. Channel channel = CHANNEL_FROM_SOMEWHERE;
  3. ChannelFuture future = channel.connect(new InetSocketAddress("127.0.0.1", 9080));
  4. future.addListener(new ChannelFutureListener() {
  5. @Override
  6. public void operationComplete(ChannelFuture future) throws Exception {
  7. if(future.isSuccess()) {
  8. ByteBuf buf = Unpooled.copiedBuffer("hello", Charset.defaultCharset());
  9. ChannelFuture wf = future.channel().writeAndFlush(buf);
  10. // ...
  11. } else {
  12. // 失敗後可嘗試重連/切換鏈路
  13. future.cause().printStackTrace();
  14. }
  15. }
  16. })
  17. }

1.3.4 事件和ChannelHandler

1. 事件:發生某種事件觸發適當的動作。比如入站觸發事件: 鏈路激活(channelActive)/數據可讀(channelRead)/發生異常(exceptionCaught)/...

2. Channelhandler:一組爲了響應特定事件而被執行的回調函數。如:ChannelInboundHanderAdapter.java是一個入站事件

1.3.5 Channel和EventLoop關係:

Channel和EventLoop都是Netty核心概念,而且有一些約定俗成的規定,能幫助編程和理解:

1. 單個Channel只會映射到單個EventLoop

2. 單個EventLoop可以處理多個Channel(1:n關係)

3. 一個EventLoop在其生命週期內只能綁定到一個線程上4. 由於單個Channel在其生命週期中只會有一個I/O線程,所以ChannelPipeline中多個ChannelHandler無需關心同步互斥問題

2. 第一款Netty應用程序

1. ChannelHandler用於構建應用業務邏輯。往往封裝了爲響應特定事件而編寫的回調函數

2. 本節主要講解一個超級簡單的Netty應用程序,回顯服務: 客戶端建立連接後,發送一個或多個消息。服務端收到後,將消息返回。

2.3 編寫Echo服務器

Netty服務端至少需要兩個部分: 一個ChannelHandler + 引導(Bootstrap)

2.3.1 ChannelHandler和業務邏輯

繼承ChannelInboundHandlerAdapter類,感興趣的入站方法:

1. channelRead() - 對於每個傳入的消息都要調用

2. channelReadComplete() - 當前批量讀取中的最後一條數據

3. exceptionCaught() - 讀取操作期間,有異常拋出時調用

代碼:

  1. @ChannelHandler.Sharable
  2. public class EchoServerHandler extends ChannelInboundHandlerAdapter{
  3. /**
  4. * 每次傳入的消息都要調用
  5. */
  6. @Override
  7. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  8. ByteBuf in = (ByteBuf) msg;
  9. System.out.println(
  10. "Server received: " + in.toString(CharsetUtil.UTF_8));
  11. ctx.write(in);
  12. }
  13. /**
  14. * 讀完當前批量中的最後一條數據後,觸發channelReadComplete(...)方法
  15. */
  16. @Override
  17. public void channelReadComplete(ChannelHandlerContext ctx)
  18. throws Exception {
  19. ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
  20. .addListener(ChannelFutureListener.CLOSE);
  21. }
  22. /**
  23. * 異常捕獲
  24. */
  25. @Override
  26. public void exceptionCaught(ChannelHandlerContext ctx,
  27. Throwable cause) {
  28. cause.printStackTrace();
  29. ctx.close();
  30. }
  31. }

解釋:

1. channelRead和channelReadComplete理解:當批量消息後最後一條數據被channelRead(...)後觸發channelReadComplete事件。

2. ctx.write(...)只是將消息暫時存放在ChannelOutboundBuffer中,等待flush(...)操作

3. @Sharable註解:本質是聲明該ChannelHandler全局單例。可被多個Channel安全的共享。標註了@Sharable註解的ChannelHandler請注意不能有對應的狀態

4. 完整代碼地址

2.3.2 引導服務器

1. 引導服務器主要打開Netty的Channel。並分配對應的EventLoop和ChannelPipeline。

2. 一個Channel只有一個ChannelPipeline。ChannelPipeline是由一組ChannelHandler組成的責任鏈。

代碼:

  1. EventLoopGroup group = new NioEventLoopGroup();
  2. try {
  3. ServerBootstrap b = new ServerBootstrap();
  4. b.group(group)
  5. .channel(NioServerSocketChannel.class)
  6. .localAddress(new InetSocketAddress(port))
  7. .childHandler(new ChannelInitializer<SocketChannel>() {
  8. @Override
  9. public void initChannel(SocketChannel ch) throws Exception {
  10. ch.pipeline().addLast(new EchoServerHandler());
  11. }
  12. });
  13. } finally {
  14. group.shutdownGracefully().sync();
  15. }

2.4 編寫Echo客戶端

客戶端將會:

1. 建立連接

2. 發送消息

3. 關閉連接

2.4.1 ChannelHandler客戶端邏輯

1. Java是通過GC可達性分析來實現垃圾回收。對於Netty傳輸中的ByteBuf,使用的是引用計數算法。也就是說:如果你使用了Netty,需要你親自考慮是否需要手動釋放對象。判斷方法,後文將會給出

2. 擴展SimpleChannelInboundHandler類處理任務的Handler,無需手動釋放對象。SimpleChannelInboundHandler.java中方法channelRead()中會負責釋放引用。

3. 客戶端發送消息條數和服務端接收的消息條數是不對應的。除非處理了TCP的粘包黏包。

代碼:

  1. // SimpleChannelInboundHandler<T>中channelRead方法負責釋放對象msg引用
  2. public abstract class SimpleChannelInboundHandler<I> ...{
  3. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  4. boolean release = true;
  5. try {
  6. // ...
  7. } finally {
  8. if (autoRelease && release) {
  9. // 減少對象msg引用計數
  10. ReferenceCountUtil.release(msg);
  11. }
  12. }
  13. }
  14. }

問:ChannelHandler中何時需要主動釋放引用?

1. 擴展的類不是: SimpleChannelInboundHandler,且該對象msg不會傳給下一個ChannelHandler

2. 擴展的類不是: SimpleChannelInboundHandler,且該對象msg不會被ctx.write(...)

2.4.2 引導客戶端

給出引導客戶端關鍵代碼,完整代碼請參考地址

代碼:

  1. EventLoopGroup group = new NioEventLoopGroup();
  2. try {
  3. Bootstrap b = new Bootstrap();
  4. b.group(group)
  5. .channel(NioSocketChannel.class)
  6. .remoteAddress(new InetSocketAddress(host, port))
  7. .handler(new ChannelInitializer<SocketChannel>() {
  8. @Override
  9. public void initChannel(SocketChannel ch)
  10. throws Exception {
  11. ch.pipeline().addLast(
  12. new EchoClientHandler());
  13. }
  14. });
  15. // 下面兩行代碼可以刪除
  16. ChannelFuture f = b.connect().sync();
  17. f.channel().closeFuture().sync();
  18. } finally {
  19. group.shutdownGracefully().sync();
  20. }

附錄

1.完整代碼地址

2.netty-in-action下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章