一、前言
前面已經學習完了Java NIO的內容,接著來學習Netty,本篇將通過一個簡單的應用來了解Netty的使用。
二、Netty應用
2.1 服務端客戶端框架圖
下圖展示了Netty中服務端與客戶端在之間的關係,客戶端連線至伺服器,然後兩者之間互相通訊,伺服器可連線多個客戶端。
2.2 服務端
服務端主要包含兩部分內容,分為引導和實現伺服器處理器。引導用於設定埠號等資訊,處理器主要是用於處理使用者自定義邏輯。
1. 引導服務端
引導服務端類名為EchoServer,其程式碼如下
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import java.net.InetSocketAddress; public class EchoServer { private int port; public EchoServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(group) .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoServerHandler()); } }); // 繫結埠,開始接收連線 ChannelFuture f = b.bind().sync(); System.out.println("Server start listen at " + port); // 等待伺服器socket關閉 f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port; if (args.length > 0) { port = Integer.parseInt(args[0]); } else { port = 8080; } new EchoServer(port).start(); } }
說明:其流程大致如下
① 建立NioEventLoopGroup例項來處理事件,如接受連線,讀寫資料等。
② 建立ServerBootstrap例項。
③ 指定服務端繫結的埠。
④ 設定childHandler來處理每一次連線。
⑤ 使用ServerBootstrap的bind方法進行繫結並同步直至其完成繫結。
2. 實現服務端邏輯
從程式碼來看引導伺服器只是完成了服務端的建立,如指定埠和處理器等,並未涉及到服務端的具體邏輯,其具體業務邏輯可以在處理器中完成,處理器需要繼承ChannelInboundHandlerAdapter,本例項中處理器為EchoServerHandler,其程式碼如下
package com.hust.grid.leesf.chapter2; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf bb = (ByteBuf) msg; bb.markReaderIndex(); System.out.println("Server received: " + ByteBufUtil .hexDump(bb.readBytes(bb.readableBytes()))); bb.resetReaderIndex(); ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) .addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
說明:當伺服器接受到訊息後,channelRead方法會被呼叫,具體訊息為msg,使用者可以對該訊息進行處理,本例中首先將接收的訊息進行轉化後列印,然後將訊息寫入ctx中,其中值得注意的是需要標記讀索引,然後恢復,否則寫入的資料為空。channelReadComplete將之前寫入客戶端的訊息重新整理,待操作完成後關閉。exceptionCaught方法則會捕捉處理中的異常。
2.3 客戶端
客戶端部分的邏輯同伺服器類似,也包含引導客戶端和實現客戶端處理器兩部分,客戶端連線服務端,並且接收服務端的訊息,關閉連線等。
1. 引導客戶端
引導客戶端類名為EchoClient,其程式碼如下
package com.hust.grid.leesf.chapter2; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.net.InetSocketAddress; public final class EchoClient { private String host; private int port; public EchoClient(String host, int port) { this.host = host; this.port = port; } public void start() throws Exception { 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 public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoClientHandler()); } }); // 啟動客戶端 ChannelFuture f = b.connect().sync(); // 直到連線關閉 f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { String host = "127.0.0.1"; int port = 8080; if (args.length == 2) { host = args[0]; port = Integer.parseInt(args[1]); } new EchoClient(host, port).start(); } }
說明:其引導部分與服務端非常類似,流程非常類似,其給出了服務端的地址和埠號,Bootstrap的connect函式將會根據指定的地址和埠號連線伺服器。
2. 實現客戶端邏輯
本部分完成使用者實際的業務邏輯,本例中的EchoClientHandler繼承SimpleChannelInboundHandler,需要重寫如下三個函式
· channelActive函式,在建立了與服務端的連線後該函式被呼叫。
· channelRead0函式,當接收到服務端傳送來的訊息後被呼叫。
· exceptionCaught函式,當處理髮生異常時被呼叫。
EchoClientHandler的程式碼如下
package com.hust.grid.leesf.chapter2; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override public void channelActive(ChannelHandlerContext ctx) { ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8)); } @Override public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) { System.out.println("Client received: " + ByteBufUtil .hexDump(in.readBytes(in.readableBytes()))); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
說明:當同伺服器的連線建立後,客戶端會傳送訊息至服務端,然後當接收到服務端傳送來的訊息時,列印該訊息。
2.4 執行
1. pom.xml檔案
由於本應用依賴的jar檔案使用maven構建,其pom.xml檔案如下。
<?xml version="1.0" encoding="UTF-8"?> <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>NettyInAction</groupId> <artifactId>com.hust.grid.leesf</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <version.jackson.core>2.6.3</version.jackson.core> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <optimize>true</optimize> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.0.32.Final</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${version.jackson.core}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${version.jackson.core}</version> </dependency> </dependencies> </project>
2. 執行服務端
啟動EchoServer,等待客戶端連線。
3. 執行客戶端
啟動EchoClient,連線服務端併傳送訊息。
其中服務端的執行結果如下。
Server start listen at 8080
Server received: 4e6574747920726f636b7321
客戶端的執行結果如下。
Client received: 4e6574747920726f636b7321
三、總結
本篇博文講解了Netty的簡單應用,通過簡單應用對Netty有所瞭解,具體的細節將會在之後的博文中進行講解,本文的程式碼已經上傳至github,也謝謝各位園友的觀看~