在本文,咱們將編寫一個基於 Netty 實現的客戶端和服務端應用程序,相信經過學習該示例,必定能更全面的理解 Netty APIbootstrap
該圖展現的是多個客戶端同時鏈接到一臺服務器。客戶端創建一個鏈接後,會向服務器發送一個或多個消息,反過來,服務器又會將每一個消息回送給客戶端安全
全部 Netty 服務器都須要如下兩部分:服務器
至少一個 CHannelHandler網絡
該組件實現了服務器對從客戶端接收的數據的處理,即它的業務邏輯異步
引導maven
配置服務器的啓動代碼,將服務器綁定到它要監聽鏈接請求的端口上ide
ChannelHandler 是一個接口族的父接口,它的實現負責接收並響應事件通知,即要包含數據的處理邏輯。咱們的 Echo 服務器須要響應傳入的消息,因此須要實現 ChannelHandler 接口,用來定義響應入站事件的方法,又由於只須要用到少許的方法,因此繼承 ChannelHandlerAdapter 類就足夠了,它提供了 ChannelHandler 的默認實現oop
咱們感興趣的方法有:學習
channelRead()this
對於每一個傳入的消息都要調用
channelReadComplete()
通知 ChannelHandler 最後一次對 channelRead() 的調用是當前批量讀取的最後一條消息
exceptionCaught
在讀取操做期間,有異常拋出時會調用
Echo 服務器的 ChannelHandler 實現 EchoServerHandler 以下
@ChannelHandler.Sharable // 標識一個 ChannelHandler 能夠被多個 Channel 安全的共享 public class EchoServerHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf in = (ByteBuf) msg; System.out.println("Server receiver: " + in.toString(CharsetUtil.UTF_8)); // 將接收到的消息寫給發送者 ctx.write(in); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { // 將剩餘的消息所有沖刷到遠程結點,並關閉 CHannel ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) .addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
應用程序經過實現或者擴展 ChannelHandler 來掛鉤到事件的生命週期,而且提供自定義的應用程序邏輯。ChannelHandler 有助於保持業務邏輯與網絡處理代碼的分離,簡化了開發過程
編寫完 EchoServerHandler 實現的核心業務邏輯以後,咱們如今探討引導服務器的過程,具體涉及內容以下:
EchoServer 類完整代碼以下
public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: " + EchoServer.class.getSimpleName() + ""); return; } int port = Integer.parseInt(args[0]); new EchoServer(port).start(); } public void start() throws Exception { final EchoServerHandler serverHandler = new EchoServerHandler(); EventLoopGroup group = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(group) // 指定所使用的 NIO 傳輸 Channel .channel(NioServerSocketChannel.class) // 使用指定的端口設置套接字地址 .localAddress(new InetSocketAddress(port)) // 添加一個 EchoServerHandler 到子 Handler 的 ChannelPipeline .childHandler(new ChannelInitializer<>() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(serverHandler); } }); // 異步地綁定服務器,調用 sync() 方法阻塞等待直到綁定完成 ChannelFuture f = b.bind().sync(); // 獲取 Channel 的 CloseFuture,而且阻塞當前線程直到它完成 f.channel().closeFuture().sync(); } finally { // 關閉 EventLoopGroup 釋放全部資源 group.shutdownGracefully().sync(); } } }
到此爲止,咱們回顧一下服務器實現中的幾個重要步驟:
引導服務器過程的重要步驟以下:
Echo 客戶端的做用:
和服務器同樣,編寫客戶端所涉及的主要代碼部分也是業務邏輯和引導
客戶端也要有一個用來處理數據的 ChannelHandler,這裏選擇 SimpleChannelInboundHandler 類處理全部必需的任務,要求重寫下面的方法:
channelActive()
當與服務器的鏈接創建以後被調用
messageReceived()
當從服務器接收到一條消息時被調用
exceptionCaught()
在處理過程當中引起異常時被調用
@ChannelHandler.Sharable public class EchoClientHandler extends SimpleChannelInboundHandler{ @Override public void channelActive(ChannelHandlerContext ctx) { // 當一個鏈接創建時被調用,發送一條消息 ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8)); } @Override protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) { // 記錄已接收消息的轉儲 System.out.println("Client received: " + msg.toString(CharsetUtil.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 發生異常時,記錄錯誤並關閉 Channel cause.printStackTrace(); ctx.close(); } }
引導客戶端相似於服務器,不一樣的是,客戶端是使用主機和端口參數來鏈接遠程地址
public class EchoClient { private final String host; private final int port; public EchoClient(String host, int port) { this.host = host; this.port = port; } public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: " + EchoClient.class.getSimpleName() + ""); return; } String host = args[0]; int port = Integer.parseInt(args[1]); new EchoClient(host, port).start(); } public void start() throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { // 建立 Bootstrap Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .remoteAddress(new InetSocketAddress(host, port)) .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoClientHandler()); } }); // 鏈接到遠程節點,阻塞等待直到鏈接完成 ChannelFuture future = bootstrap.connect().sync(); // 阻塞,直到 Channel 關閉 future.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } }
到此爲止,咱們回顧一下客戶端實現中的幾個重要步驟:
本文的項目使用 maven 構建,先啓動服務端並準備好接受鏈接。而後啓動客戶端,一旦客戶端創建鏈接,就會發送消息。服務器接收消息,控制檯會打印以下信息:
Server receiver: Netty rocks!
同時將其回送給客戶端,客戶端的控制檯也會打印以下消息,隨後退出:
Client received: Netty rocks!