為什麼使用Netty Netty是業界最流行的NIO框架之一,它的健壯性、功能、性能、可定製性、可擴展性在同類框架中都是首屈一指的,它已經得到了成百上千的商用項目的證明。對於為什麼使用Netty這個話題,我們先看一下使用原生的NIO有什麼缺點: NIO的類庫和API繁雜,使用麻煩,需要熟練掌握Sel ...
為什麼使用Netty
Netty是業界最流行的NIO框架之一,它的健壯性、功能、性能、可定製性、可擴展性在同類框架中都是首屈一指的,它已經得到了成百上千的商用項目的證明。對於為什麼使用Netty這個話題,我們先看一下使用原生的NIO有什麼缺點:
- NIO的類庫和API繁雜,使用麻煩,需要熟練掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等,這就像我們會使用Hibernate、MyBatis這些ORM框架而不會直接使用Connection、Statement一樣
- 需要其他額外技能作為鋪墊,必須對多線程和網路編程非常熟悉才能寫出高質量的NIO程式
- 可靠性能力補齊,工作量和難度都非常大,例如客戶端面臨斷線重連、網路閃斷、半包讀寫、失敗緩存、網路擁塞、異常碼流等問題的處理
- JDK NIO的BUG,例如著名的epoll bug,該問題會導致Selector空輪訓,最終導致CPU 100%
也正是因為有種種缺點,因此不建議使用原生的NIO而是建議使用一些比較成熟的NIO框架例如Netty、Mina,這一系列文章講的是Netty,Netty作為一款高性能NIO框架,其優點總結有:
- API使用簡單、開發門檻低
- 功能強大,預置了多種編碼解碼功能,支持多種主流協議
- 定製能力強,可以通過ChannelHandler對通信框架進行靈活擴展
- 性能高,與業界其他主流NIO框架對比,Netty性能最優
- 成熟、穩定,Netty修複了已經發現的所有JDK NIO的BUG,業務開發人員不需要再為NIO的BUG而煩惱
- 社區活躍、版本迭代周期短,發現的BUG可以被及時修複,同時,更多的新功能會被加入
- 經歷了大規模的商業應用考驗,質量得到驗證
正因為這些優點,Netty逐漸成為了Java NIO變成的首選框架。
Netty入門Demo
下麵演示一下Netty的Demo(註:Demo來自Netty權威指南第三章),本文只寫代碼與演示結果,不做講解,對Netty的使用基本講解放在下一篇文章中,循序漸進,先感性地認識Netty,再理性地認識Netty中的東西。
從服務端代碼開始,定義一個TimeServer:
1 public class TimeServer { 2 3 public void bind(int port) throws Exception { 4 // NIO線程組 5 EventLoopGroup bossGroup = new NioEventLoopGroup(); 6 EventLoopGroup workerGroup = new NioEventLoopGroup(); 7 8 try { 9 ServerBootstrap b = new ServerBootstrap(); 10 b.group(bossGroup, workerGroup) 11 .channel(NioServerSocketChannel.class) 12 .option(ChannelOption.SO_BACKLOG, 1024) 13 .childHandler(new ChildChannelHandler()); 14 15 // 綁定埠,同步等待成功 16 ChannelFuture f = b.bind(port).sync(); 17 // 等待服務端監聽埠關閉 18 f.channel().closeFuture().sync(); 19 } finally { 20 // 優雅退出,釋放線程池資源 21 bossGroup.shutdownGracefully(); 22 workerGroup.shutdownGracefully(); 23 } 24 } 25 26 private class ChildChannelHandler extends ChannelInitializer<SocketChannel> { 27 @Override 28 protected void initChannel(SocketChannel arg0) throws Exception { 29 arg0.pipeline().addLast(new TimeServerHandler()); 30 } 31 } 32 33 }
TimeServerHandler這麼定義:
1 public class TimeServerHandler extends ChannelHandlerAdapter { 2 3 @Override 4 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 5 ByteBuf buf = (ByteBuf)msg; 6 byte[] req = new byte[buf.readableBytes()]; 7 buf.readBytes(req); 8 9 String body = new String(req, "UTF-8"); 10 System.out.println("The time server receive order:" + body); 11 String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER"; 12 13 ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); 14 ctx.write(resp); 15 } 16 17 @Override 18 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 19 ctx.flush(); 20 } 21 22 @Override 23 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 24 ctx.close(); 25 } 26 27 }
即讀取來自客戶端的數據,如果是"QUERY TIME ORDER",則把當前時間寫到Channel中去。至此,Netty服務端代碼已經開發完畢。接下來是Netty客戶端代碼,首先還是TimeClient:
1 public class TimeClient { 2 3 public void connect(int port, String host) throws Exception { 4 EventLoopGroup group = new NioEventLoopGroup(); 5 try { 6 Bootstrap b = new Bootstrap(); 7 8 b.group(group) 9 .channel(NioSocketChannel.class) 10 .option(ChannelOption.TCP_NODELAY, true) 11 .handler(new ChannelInitializer<SocketChannel>() { 12 protected void initChannel(SocketChannel ch) throws Exception { 13 ch.pipeline().addLast(new TimeClientHandler()); 14 }; 15 }); 16 17 // 發起非同步連接操作 18 ChannelFuture f = b.connect(host, port).sync(); 19 // 等待客戶端連接關閉 20 f.channel().closeFuture().sync(); 21 } finally { 22 // 優雅退出,釋放NIO線程組 23 group.shutdownGracefully(); 24 } 25 } 26 27 }
同樣的,定義一個TimeClientHandler:
1 public class TimeClientHandler extends ChannelHandlerAdapter { 2 3 private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class); 4 5 private final ByteBuf firstMessage; 6 7 public TimeClientHandler() { 8 byte[] req = "QUERY TIME ORDER".getBytes(); 9 firstMessage = Unpooled.buffer(req.length); 10 firstMessage.writeBytes(req); 11 } 12 13 @Override 14 public void channelActive(ChannelHandlerContext ctx) throws Exception { 15 ctx.writeAndFlush(firstMessage); 16 } 17 18 @Override 19 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 20 ByteBuf buf = (ByteBuf)msg; 21 byte[] req = new byte[buf.readableBytes()]; 22 buf.readBytes(req); 23 24 String body = new String(req, "UTF-8"); 25 System.out.println("Now is:" + body); 26 } 27 28 @Override 29 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 30 LOGGER.warn("Unexcepted exception from downstream:" + cause.getMessage()); 31 ctx.close(); 32 } 33 34 }
客戶端的操作為列印來自服務端的數據,這樣,整個Netty Demo代碼就寫完了,結構比較清楚,都是一個Server+一個Handler的模式,Handler用於處理讀取到的信息。
運行Demo
上面寫完了Demo,接著寫一下測試代碼,很簡單,分別運行bind方法和connect方法即可:
1 public class CoreTest { 2 3 @Test 4 public void timeServerTest() throws Exception { 5 new TimeServer().bind(8080); 6 } 7 8 @Test 9 public void timeClientTest() throws Exception { 10 new TimeClient().connect(8080, "127.0.0.1"); 11 } 12 13 }
先運行timeServerTest讓服務端先啟動,再運行timeClientServer讓客戶端後啟動,運行結果服務端的列印為:
The time server receive order:QUERY TIME ORDER
結合代碼可以看到,服務端讀取到了來自客戶端的數據,數據內容為"QUERY TIME ORDER",接著服務端取自己的時間,傳輸給客戶端,看一下客戶端的列印:
Now is:Thu Apr 05 21:07:39 CST 2018
列印了來自服務端的時間,這樣,利用Netty進行服務端+客戶端的相互通信的Demo完成,有了這個Demo,對Netty有了感性上的認識,接著我們一點一點深入去學習Netty。