Netty 入門初體驗
這篇主要介紹一個Netty 客戶端與服務端的示例程式碼,對Netty有一個直觀感受,看看如何使用Netty,後續文章會對Netty的各個元件進行詳細分析
Netty簡介
Netty是一款非同步的事件驅動的網路應用程式框架,支援快速開發可維護的高效能 的面向協議的伺服器和客戶端。Netty主要是對java 的 nio包進行的封裝
為什麼要使用 Netty
上面介紹到 Netty是一款高效能的網路通訊框架 ,那麼我們為什麼要使用Netty,換句話說,Netty有哪些優點讓我們值得使用它,為什麼不使用原生的 Java Socket程式設計,或者使用 Java 1.4引入的 Java IO/">NIO。接下來分析分析 Java Socket程式設計和 Java NIO。
Java 網路程式設計
首先來看一個Java 網路程式設計的例子:
public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(8888); Socket socket = serverSocket.accept(); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = ""; while ((line = reader.readLine()) != null) { System.out.println("received: " + line); } } catch (IOException e) { e.printStackTrace(); } } 複製程式碼
上面展示了一個簡單的Socket服務端例子,該程式碼只能同時處理一個連線,要管理多個併發客戶端,需要為每個新的客戶端Socket建立一個 新的Thread。這種併發方案對於中小數量的客戶端來說還可以接受,如果是針對高併發,超過100000的併發連線來說該方案並不可取,它所需要的執行緒資源太多,而且任何時候都可能存在大量執行緒處於阻塞狀態,等待輸入或者輸出資料就緒,整個方案效能太差。所以,高併發的場景,一般的Java 網路程式設計方案是不可取的。
Java NIO
還是先來看一個 Java NIO的例子:
public class ServerSocketChannelDemo { private ServerSocketChannel serverSocketChannel; private Selector selector; public ServerSocketChannelDemo(int port) throws IOException { serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(port)); selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } public void listener() throws IOException { while (true) { int n = selector.select(); if (n == 0) { continue; } Iterator iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = (SelectionKey) iterator.next(); if (selectionKey.isAcceptable()) { ServerSocketChannel server_channel = (ServerSocketChannel) selectionKey.channel(); SocketChannel socketChannel = server_channel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } if (selectionKey.isReadable()) { //如果通道處於讀就緒的狀態 //讀操作 //TODO } } } } } 複製程式碼
NIO的核心部分主要有:
- 通道 Channel
- 緩衝區 Buffer
- 多路複用器 Selector
選擇器 Selector 是 Java 非阻塞 I/O實現的關鍵,將通道Channel註冊在 Selector上,如果某個通道 Channel傳送 讀或寫事件,這個Channel處於就緒狀態,會被Selector輪詢出來,進而進行後續I/O操作。這種I/O多路複用的方式相比上面的阻塞 I/O模型,提供了更好的資源管理:
- 使用較少的執行緒便可以處理很多連線,因此也減少了記憶體管理和上下文切換所帶來的開銷
- 當沒有I/O操作需要處理的時候,執行緒也可以被用於其他任務。
儘管使用 Java NIO可以讓我們使用較少的執行緒處理很多連線,但是在高負載下可靠和高效地處理和排程I/O操作是一項繁瑣而且容易出錯的任務 ,所以才引出了高效能網路程式設計專家——Netty
Netty特性
Netty有很多優秀的特性值得讓我們去使用它(摘自《Netty實戰》):
設計
效能
- 更好的吞吐量,低延遲
- 更低的資源消耗
- 最少的記憶體複製
健壯性
- 不再因過快、過慢或超負載連線導致OutOfMemoryError
- 不再有在高速網路環境下NIO讀寫頻率不一致的問題
安全性:
- 完整的SSL/TLS和STARTTLS的支援
- 可用於受限環境下,如 Applet 和OSGI
易用:
- 詳實的Javadoc和大量的示例集
- 不需要超過 JDK 1.6+的依賴
Netty示例程式碼
下面是server 和client的示例程式碼,先來看看Netty程式碼是怎麼樣的,後續文章會詳細分析各個模組。
Server程式碼
public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public static void main(String[] args) throws InterruptedException { new EchoServer(8888).start(); } public void start() throws InterruptedException { final EchoServerHandler serverHandler = new EchoServerHandler(); //建立EventLoopGroup,處理事件 EventLoopGroup boss = new NioEventLoopGroup(); EventLoopGroup worker = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(boss,worker) //指定所使用的NIO傳輸 Channel .channel(NioServerSocketChannel.class) //使用指定的埠設定套接字地址 .localAddress(new InetSocketAddress(port)) //新增一個EchoServerHandler到子Channel的ChannelPipeline .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //EchoServerHandler標誌為@Shareable,所以我們可以總是使用同樣的例項 socketChannel.pipeline().addLast(serverHandler); } }); //非同步的繫結伺服器,呼叫sync()方法阻塞等待直到繫結完成 ChannelFuture future = b.bind().sync(); future.channel().closeFuture().sync(); } finally { //關閉EventLoopGroup,釋放所有的資源 group.shutdownGracefully().sync(); worker.shutdownGracefully().sync(); } } } 複製程式碼
EchoServerHandler
@ChannelHandler.Sharable //標識一個 ChannelHandler可以被多個Channel安全地共享 public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buffer = (ByteBuf) msg; //將訊息記錄到控制檯 System.out.println("Server received: " + buffer.toString(CharsetUtil.UTF_8)); //將接受到訊息回寫給傳送者 ctx.write(buffer); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { //將未訊息沖刷到遠端節點,並且關閉該 Channel ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) .addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //列印異常棧跟蹤 cause.printStackTrace(); //關閉該Channel ctx.close(); } } 複製程式碼
程式碼要點解讀:
-
ServerBootStrap
是引導類,幫助服務啟動的輔助類,可以設定 Socket引數 -
EventLoopGroup
是處理I/O操作的執行緒池,用來分配 服務於Channel的I/O和事件的EventLoop
,而NioEventLoopGroup
是EventLoopGroup
的一個實現類。這裡例項化了兩個NioEventLoopGroup
,一個boss
,主要用於處理客戶端連線,一個worker
用於處理客戶端的資料讀寫工作 -
EchoServerHandler
實現了業務邏輯 -
通過呼叫
ServerBootStrap.bind()
方法以繫結伺服器
Client 程式碼
public class EchoClient { private final String host; private final int port; public EchoClient(String host, int port) { this.host = host; this.port = port; } public void start() throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .remoteAddress(new InetSocketAddress(host, port)) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new EchoClientHandler()); } }); ChannelFuture channelFuture = b.connect().sync(); channelFuture.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } public static void main(String[] args) throws InterruptedException { new EchoClient("127.0.0.1", 8888).start(); } } 複製程式碼
EchoClientHandler
@ChannelHandler.Sharable public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception { System.out.println("Client received: "+byteBuf.toString()); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks",CharsetUtil.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 複製程式碼
程式碼要點解讀:
-
為初始化客戶端,建立了一個BootStrap例項,與
ServerBootStrap
一樣,也是一個引導類,主要輔助客戶端 -
分配了一個
NioEventLoopGroup
例項,裡面的EventLoop
,處理連線的生命週期中所發生的事件 -
EchoClientHandler
類負責處理業務邏輯,與服務端的EchoSeverHandler
作用相似。