為什麼使用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中的東西。
提一下,本文及之後的文章Netty基於5.0.0.Alpha1這個版本,貼一下我自己的Maven配置吧:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.xrq.netty</groupId> <artifactId>netty-test</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies> </project>
首先從服務端程式碼開始,定義一個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。