沉淀再出發:關於netty的一些理解和使用
一、前言
Netty是由JBOSS提供的一個java開源框架。Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。也就是說,Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶、服務端應用。Netty相當於簡化和流線化了網絡應用的編程開發過程,例如:基於TCP和UDP的socket服務開發。“快速”和“簡單”並不用產生維護性或性能上的問題。Netty 是一個吸收了多種協議(包括FTP、SMTP、HTTP等各種二進制文本協議)的實現經驗,並經過相當精心設計的項目。最終,Netty 成功的找到了一種方式,在保證易於開發的同時還保證了其應用的性能,穩定性和伸縮性。netty在底層的數據通信和封裝之中有着重要的作用,下面我們就來看看netty的簡單使用過程,以及背后的原理。
二、netty的簡單使用
2.1、netty的環境部署和使用
在這里我們使用myeclipse平台,maven管理工具進行開發,其實使用eclipse或者其他軟件也可以。首先我們新建一個maven項目,項目名和包名自定:
之后我們修改pom.xml文件,增加netty依賴:
保存之后,系統就會自動為我們下載和安裝了,非常的方便,這樣,我們的環境就部署完畢了。
2.2、一個簡單的案例
下面我們看一個簡單地案例:
我們新建一個包,然后寫入兩個文件:
首先我們編寫一個處理連接的類 HelloServerHandler :
1 package com.coder.server; 2 3 import io.netty.buffer.ByteBuf; 4 import io.netty.channel.ChannelHandlerContext; 5 import io.netty.channel.ChannelInboundHandlerAdapter; 6 import io.netty.util.CharsetUtil; 7 import io.netty.util.ReferenceCountUtil; 8 9 10 public class HelloServerHandler extends ChannelInboundHandlerAdapter { 11 /** 12 * 收到數據時調用 13 */ 14 @Override 15 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 16 try { 17 ByteBuf in = (ByteBuf)msg; 18 System.out.print(in.toString(CharsetUtil.UTF_8)); 19 } finally { 20 // 拋棄收到的數據 21 ReferenceCountUtil.release(msg); 22 } 23 } 24 25 /** 26 * 當Netty由於IO錯誤或者處理器在處理事件時拋出異常時調用 27 */ 28 @Override 29 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 30 // 當出現異常就關閉連接 31 cause.printStackTrace(); 32 ctx.close(); 33 } 34 }
其次,我們編寫接收連接,並且派發和處理的類 HelloServer :
1 package com.coder.server; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.ChannelOption; 7 import io.netty.channel.EventLoopGroup; 8 import io.netty.channel.nio.NioEventLoopGroup; 9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioServerSocketChannel; 11 12 public class HelloServer { 13 private int port; 14 15 public HelloServer(int port) { 16 this.port = port; 17 } 18 19 public void run() throws Exception { 20 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用來接收進來的連接 21 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用來處理已經被接收的連接 22 System.out.println("准備運行端口:" + port); 23 24 try { 25 ServerBootstrap b = new ServerBootstrap(); 26 b.group(bossGroup, workerGroup) 27 .channel(NioServerSocketChannel.class) // 這里告訴Channel如何接收新的連接 28 .childHandler( new ChannelInitializer<SocketChannel>() { 29 @Override 30 protected void initChannel(SocketChannel ch) throws Exception { 31 // 自定義處理類 32 ch.pipeline().addLast(new HelloServerHandler()); 33 } 34 }) 35 .option(ChannelOption.SO_BACKLOG, 128) 36 .childOption(ChannelOption.SO_KEEPALIVE, true); 37 38 // 綁定端口,開始接收進來的連接 39 ChannelFuture f = b.bind(port).sync(); 40 41 // 等待服務器socket關閉 42 f.channel().closeFuture().sync(); 43 } catch (Exception e) { 44 workerGroup.shutdownGracefully(); 45 bossGroup.shutdownGracefully(); 46 } 47 } 48 49 public static void main(String[] args) throws Exception { 50 int port = 12345; 51 new HelloServer(port).run(); 52 } 53 }
然后運行,等待連接就好了,那么問題來了,使用什么進行連接呢?在windows中,我們可以使用Telnet,這個比較方便和簡單,但是我們需要打開控制面板的程序和功能模塊,並且啟動服務,之后最好重啟一下電腦:
下面我們運行程序,並使用Telnet客戶端測試一下:
在telnet中‘ctrl+]’可以顯示輸入的文字,否則將看不到輸入。
三、使用netty自定義時間服務器
本例中我們試圖在服務器和客戶端連接被創立時發送一個消息,然后在客戶端解析收到的消息並輸出。並且,在這個項目中使用 POJO 代替 ByteBuf 來作為傳輸對象。
3.1、pojo對象創建
Time 類:
1 package com.coder.pojo; 2 3 import java.util.Date; 4 5 /** 6 * 自定義時間數據類 7 * 8 */ 9 public class Time { 10 private final long value; 11 12 public Time() { 13 // 除以1000是為了使時間精確到秒
//注意這里的this,其實就是調用了 public Time(long value) ,並且更加的方便和快捷。 14 this(System.currentTimeMillis() / 1000L); 15 } 16 17 public Time(long value) { 18 this.value = value; 19 } 20 21 public long value() { 22 return value; 23 } 24 25 @Override 26 public String toString() { 27 return new Date((value()) * 1000L).toString(); 28 } 29 }
3.2、服務器程序
TimeEncoderPOJO類:
1 package com.coder.server; 2 3 import com.coder.pojo.Time; 4 5 import io.netty.buffer.ByteBuf; 6 import io.netty.channel.ChannelHandlerContext; 7 import io.netty.handler.codec.MessageToByteEncoder; 8 9 /** 10 * 服務器數據編碼類 11 * 12 */ 13 public class TimeEncoderPOJO extends MessageToByteEncoder<Time> { 14 15 // 發送數據時調用 16 @Override 17 protected void encode(ChannelHandlerContext ctx, Time msg, ByteBuf out) throws Exception { 18 // 只傳輸當前時間,精確到秒 19 out.writeInt((int)msg.value()); 20 } 21 22 }
TimeServerHandlerPOJO類:連接建立並且准備通信的時候進行處理,發送當前時間,並增加監聽。
1 package com.coder.server; 2 3 import com.coder.pojo.Time; 4 5 import io.netty.channel.ChannelFuture; 6 import io.netty.channel.ChannelFutureListener; 7 import io.netty.channel.ChannelHandlerContext; 8 import io.netty.channel.ChannelInboundHandlerAdapter; 9 10 /** 11 * 服務器解碼器 12 * 連接建立時發送當前時間 13 * 14 */ 15 public class TimeServerHandlerPOJO extends ChannelInboundHandlerAdapter { 16 /** 17 * 連接建立的時候並且准備進行通信時被調用 18 */ 19 @Override 20 public void channelActive(final ChannelHandlerContext ctx) throws Exception { 21 // 發送當前時間信息 22 ChannelFuture f = ctx.writeAndFlush(new Time()); 23 // 發送完畢之后關閉 Channel 24 f.addListener(ChannelFutureListener.CLOSE); 25 } 26 27 @Override 28 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 29 cause.printStackTrace(); 30 ctx.close(); 31 } 32 }
TimeServerPOJO類:服務器的主程序
1 package com.coder.server; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.ChannelOption; 7 import io.netty.channel.EventLoopGroup; 8 import io.netty.channel.nio.NioEventLoopGroup; 9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioServerSocketChannel; 11 12 public class TimeServerPOJO { 13 private int port; 14 15 public TimeServerPOJO(int port) { 16 this.port = port; 17 } 18 19 public void run() throws Exception { 20 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用來接收進來的連接 21 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用來處理已經被接收的連接 22 System.out.println("准備運行端口:" + port); 23 24 try { 25 ServerBootstrap b = new ServerBootstrap(); // 啟動NIO服務的輔助啟動類 26 b.group(bossGroup, workerGroup) 27 .channel(NioServerSocketChannel.class) // 這里告訴Channel如何接收新的連接 28 .childHandler( new ChannelInitializer<SocketChannel>() { 29 @Override 30 protected void initChannel(SocketChannel ch) throws Exception { 31 // 自定義處理類 32 // 注意添加順序 33 ch.pipeline().addLast(new TimeEncoderPOJO(),new TimeServerHandlerPOJO()); 34 } 35 }) 36 .option(ChannelOption.SO_BACKLOG, 128) 37 .childOption(ChannelOption.SO_KEEPALIVE, true); 38 39 // 綁定端口,開始接收進來的連接 40 ChannelFuture f = b.bind(port).sync(); 41 42 // 等待服務器socket關閉 43 f.channel().closeFuture().sync(); 44 } catch (Exception e) { 45 workerGroup.shutdownGracefully(); 46 bossGroup.shutdownGracefully(); 47 } 48 } 49 50 public static void main(String[] args) throws Exception { 51 int port = 12345; 52 new TimeServerPOJO(port).run(); 53 } 54 }
其中ch.pipeline().addLast(new TimeEncoderPOJO(),new TimeServerHandlerPOJO());方法的含義為:Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in its ChannelPipeline
.也就是說當我們添加一些處理的時候會按照管道的方式,一步步的處理,因此先后順序非常重要。
3.3、客戶端程序
先來看看解碼器(服務器端發送了編碼后的時間信息,因此,這里客戶端收到之后需要解碼):
TimeDecoderPOJO 類:
1 package com.coder.client; 2 3 import java.util.List; 4 5 import com.coder.pojo.Time; 6 7 import io.netty.buffer.ByteBuf; 8 import io.netty.channel.ChannelHandlerContext; 9 import io.netty.handler.codec.ByteToMessageDecoder; 10 11 public class TimeDecoderPOJO extends ByteToMessageDecoder { 12 /** 13 * 有新數據接收時調用 14 * 為防止分包現象,先將數據存入內部緩存,到達滿足條件之后再進行解碼 15 */ 16 @Override 17 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 18 if(in.readableBytes() < 4) { 19 return; 20 } 21 22 // out添加對象則表示解碼成功 23 out.add(new Time(in.readUnsignedInt())); 24 } 25 }
再看看客戶端數據處理類:
TimeClientHandlerPOJO類:
1 package com.coder.client; 2 3 import com.coder.pojo.Time; 4 5 import io.netty.channel.ChannelHandlerContext; 6 import io.netty.channel.ChannelInboundHandlerAdapter; 7 8 /** 9 * 客戶端數據處理類 10 * 11 */ 12 public class TimeClientHandlerPOJO extends ChannelInboundHandlerAdapter { 13 @Override 14 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 15 // 直接將信息轉換成Time類型輸出即可 16 Time time = (Time)msg; 17 System.out.println(time); 18 ctx.close(); 19 } 20 21 @Override 22 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 23 cause.printStackTrace(); 24 ctx.close(); 25 } 26 }
最后是客戶端的主程序:
TimeClientPOJO類:
1 package com.coder.client; 2 3 import io.netty.bootstrap.Bootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.ChannelOption; 7 import io.netty.channel.EventLoopGroup; 8 import io.netty.channel.nio.NioEventLoopGroup; 9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioSocketChannel; 11 12 public class TimeClientPOJO { 13 public static void main(String[] args) throws Exception{ 14 String host = "127.0.0.1"; // ip 15 int port = 12345; // 端口 16 EventLoopGroup workerGroup = new NioEventLoopGroup(); 17 18 try { 19 Bootstrap b = new Bootstrap(); // 與ServerBootstrap類似 20 b.group(workerGroup); // 客戶端不需要boss worker 21 b.channel(NioSocketChannel.class); 22 b.option(ChannelOption.SO_KEEPALIVE, true); // 客戶端的socketChannel沒有父親 23 b.handler(new ChannelInitializer<SocketChannel>() { 24 @Override 25 protected void initChannel(SocketChannel ch) throws Exception { 26 // POJO 27 ch.pipeline().addLast(new TimeDecoderPOJO() ,new TimeClientHandlerPOJO()); 28 } 29 }); 30 31 // 啟動客戶端,客戶端用connect連接 32 ChannelFuture f = b.connect(host, port).sync(); 33 34 // 等待連接關閉 35 f.channel().closeFuture().sync(); 36 } finally { 37 workerGroup.shutdownGracefully(); 38 } 39 } 40 }
至此程序編寫完畢,先運行服務器,再運行客戶端程序,然后測試即可,我們會發現服務器一直等待着請求,當客戶端連接上之后,服務器就會發出帶着格式的時間,客戶端接收到之后進行解碼,然后顯示出來並且退出。在同一個myeclipse之中可以運行多個程序,使用下圖中的按鈕可以進行切換。
四、netty的基本組成部分
4.1、Channel
Channel 是 Java NIO 的一個基本構造。它代表一個到實體(如一個硬件設備、一個文件、一個網絡套接字或者一個能夠執行一個或者多個不同的I/O操作的程序組件)的開放連接,如讀操作和寫操作。目前,可以把 Channel 看作是傳入(入站)或者傳出(出站)數據的載體。因此,它可以被打開或者被關閉,連接或者斷開連接。
4.2、Callback(回調)
Netty 在內部使用了回調來處理事件;當一個回調被觸發時,相關的事件可以被一個 interfaceChannelHandler 的實現處理。
4.3、Future
Future 提供了另一種在操作完成時通知應用程序的方式。這個對象可以看作是一個異步操作的結果的占位符;它將在未來的某個時刻完成,並提供對其結果的訪問。JDK 預置了 interface java.util.concurrent.Future,但是其所提供的實現,只允許手動檢查對應的操作是否已經完成,或者一直阻塞直到它完成。這是非常繁瑣的,所以 Netty 提供了它自己的實現ChannelFuture,用於在執行異步操作的時候使用。
4.4、Event 和 Handler
Netty 使用不同的事件來通知我們狀態的改變或者是操作的狀態。這使得我們能夠基於已經發生的事件來觸發適當的動作。這些動作可能是:記錄日志、數據轉換、流控制、應用程序邏輯。Netty 是一個網絡編程框架,所以事件是按照它們與入站或出站數據流的相關性進行分類的。可能由入站數據或者相關的狀態更改而觸發的事件包括:連接已被激活或者連接失活、數據讀取、用戶事件、錯誤事件。出站事件是未來將會觸發的某個動作的操作結果,這些動作包括:打開或者關閉到遠程節點的連接、將數據寫到或者沖刷到套接字。
Netty 的 ChannelHandler 為處理器提供了基本的抽象,目前可以認為每個 ChannelHandler 的實例都類似於一種為了響應特定事件而被執行的回調。Netty 提供了大量預定義的可以開箱即用的 ChannelHandler 實現,包括用於各種協議(如 HTTP 和 SSL/TLS)的 ChannelHandler。在內部 ChannelHandler 自己也使用了事件和 Future。
五、netty聊天程序
5.1、服務器端
SimpleChatServerInitializer類:
1 package com.coder.server; 2 3 import io.netty.channel.ChannelInitializer; 4 import io.netty.channel.ChannelPipeline; 5 import io.netty.channel.socket.SocketChannel; 6 import io.netty.handler.codec.DelimiterBasedFrameDecoder; 7 import io.netty.handler.codec.Delimiters; 8 import io.netty.handler.codec.string.StringDecoder; 9 import io.netty.handler.codec.string.StringEncoder; 10 11 /** 12 * 服務器配置初始化 13 * 添加多個處理器 14 */ 15 public class SimpleChatServerInitializer extends ChannelInitializer<SocketChannel> { 16 17 @Override 18 protected void initChannel(SocketChannel ch) throws Exception { 19 ChannelPipeline pipeline = ch.pipeline(); 20 // 添加處理類 21 // 使用'\r''\n'分割幀 22 pipeline.addLast("framer", 23 new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); 24 // 解碼、編碼器 25 pipeline.addLast("decoder", new StringDecoder()); 26 pipeline.addLast("encoder", new StringEncoder()); 27 // 處理器 28 pipeline.addLast("handler", new SimpleChatServerHandler()); 29 30 System.out.println("SimpleChatClient: " + ch.remoteAddress() + "連接上"); 31 } 32 33 }
SimpleChatServerHandler類:
1 package com.coder.server; 2 3 4 import io.netty.channel.*; 5 import io.netty.channel.group.ChannelGroup; 6 import io.netty.channel.group.DefaultChannelGroup; 7 import io.netty.util.concurrent.GlobalEventExecutor; 8 9 /** 10 * 服務端處理器 11 */ 12 public class SimpleChatServerHandler extends SimpleChannelInboundHandler<String> { 13 14 public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 15 16 /** 17 * 收到新的客戶端連接時調用 18 * 將客戶端channel存入列表,並廣播消息 19 */ 20 @Override 21 public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 22 Channel incoming = ctx.channel(); 23 // 廣播加入消息 24 channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n"); 25 channels.add(incoming); // 存入列表 26 } 27 28 /** 29 * 客戶端連接斷開時調用 30 * 廣播消息 31 */ 32 @Override 33 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 34 Channel incoming = ctx.channel(); 35 // 廣播離開消息 36 channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開\n"); 37 // channel會自動從ChannelGroup中刪除 38 } 39 40 /** 41 * 收到消息時調用 42 * 將消息轉發給其他客戶端 43 */ 44 @Override 45 protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 46 Channel incoming = ctx.channel(); 47 for(Channel channel : channels) { // 遍歷所有連接的客戶端 48 if(channel != incoming) { // 其他客戶端 49 channel.writeAndFlush("[" + incoming.remoteAddress() + "] " + msg + "\n" ); 50 } else { // 自己 51 channel.writeAndFlush("[you] " + msg + "\n" ); 52 } 53 } 54 } 55 56 /** 57 * 監聽到客戶端活動時調用 58 */ 59 @Override 60 public void channelActive(ChannelHandlerContext ctx) throws Exception { 61 Channel incoming = ctx.channel(); 62 System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 在線"); 63 } 64 65 /** 66 * 監聽到客戶端不活動時調用 67 */ 68 @Override 69 public void channelInactive(ChannelHandlerContext ctx) throws Exception { 70 Channel incoming = ctx.channel(); 71 System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 掉線"); 72 } 73 74 /** 75 * 當Netty由於IO錯誤或者處理器在處理事件拋出異常時調用 76 * 關閉連接 77 */ 78 @Override 79 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 80 Channel incoming = ctx.channel(); 81 System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 異常"); 82 } 83 }
SimpleChatServer類:
1 package com.coder.server; 2 3 import io.netty.bootstrap.ServerBootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelOption; 6 import io.netty.channel.EventLoopGroup; 7 import io.netty.channel.nio.NioEventLoopGroup; 8 import io.netty.channel.socket.nio.NioServerSocketChannel; 9 10 /** 11 * 服務端 main 啟動 12 */ 13 public class SimpleChatServer { 14 private int port; // 端口 15 16 public SimpleChatServer(int port) { 17 this.port = port; 18 } 19 20 // 配置並開啟服務器 21 public void run() throws Exception { 22 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用來接收進來的連接 23 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用來處理已接收的連接 24 25 try { 26 ServerBootstrap sb = new ServerBootstrap(); // 啟動NIO服務的輔助啟動類 27 sb.group(bossGroup, workerGroup) 28 .channel(NioServerSocketChannel.class) // 設置如何接受連接 29 .childHandler(new SimpleChatServerInitializer()) // 配置Channel 30 .option(ChannelOption.SO_BACKLOG, 128) // 設置緩沖區 31 .childOption(ChannelOption.SO_KEEPALIVE, true); // 啟用心跳機制 32 33 System.out.println("SimpleChatServer 啟動了"); 34 ChannelFuture future = sb.bind(port).sync(); // 綁定端口,開始接收連接 35 future.channel().closeFuture().sync(); // 等待關閉服務器(不會發生) 36 } finally { 37 workerGroup.shutdownGracefully(); 38 bossGroup.shutdownGracefully(); 39 System.out.println("SimpleChatServer 關閉了"); 40 } 41 } 42 43 public static void main(String[] args) throws Exception { 44 int port = 8080; 45 new SimpleChatServer(port).run(); // 開啟服務器 46 } 47 }
5.2、客戶端程序
SimpleChatClientInitializer類:
1 package com.coder.client; 2 3 import io.netty.channel.ChannelInitializer; 4 import io.netty.channel.ChannelPipeline; 5 import io.netty.channel.socket.SocketChannel; 6 import io.netty.handler.codec.DelimiterBasedFrameDecoder; 7 import io.netty.handler.codec.Delimiters; 8 import io.netty.handler.codec.string.StringDecoder; 9 import io.netty.handler.codec.string.StringEncoder; 10 11 /** 12 * 客戶端配置初始化 13 * 與服務端類似 14 */ 15 public class SimpleChatClientInitializer extends ChannelInitializer<SocketChannel> { 16 17 @Override 18 protected void initChannel(SocketChannel ch) throws Exception { 19 ChannelPipeline pipeline = ch.pipeline(); 20 // 添加處理類 21 // 使用'\r''\n'分割幀 22 pipeline.addLast("framer", 23 new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); 24 // 解碼、編碼器 25 pipeline.addLast("decoder", new StringDecoder()); 26 pipeline.addLast("encoder", new StringEncoder()); 27 // 處理器 28 pipeline.addLast("handler", new SimpleChatClientHandler()); 29 } 30 31 }
SimpleChatClientHandler類:
1 package com.coder.client; 2 3 import io.netty.channel.ChannelHandlerContext; 4 import io.netty.channel.SimpleChannelInboundHandler; 5 6 /** 7 * 客戶端處理類 8 * 直接輸出收到的消息 9 */ 10 public class SimpleChatClientHandler extends SimpleChannelInboundHandler<String> { 11 12 @Override 13 protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 14 System.out.println(msg); // 直接輸出消息 15 } 16 17 }
SimpleChatClient類:
1 package com.coder.client; 2 3 import java.io.BufferedReader; 4 import java.io.InputStreamReader; 5 6 import io.netty.bootstrap.Bootstrap; 7 import io.netty.channel.Channel; 8 import io.netty.channel.EventLoopGroup; 9 import io.netty.channel.nio.NioEventLoopGroup; 10 import io.netty.channel.socket.nio.NioSocketChannel; 11 /** 12 * 客戶端 13 * 開啟客戶端,接收控制台輸入並發送給服務端 14 */ 15 public class SimpleChatClient { 16 private final String host; // IP 17 private final int port; // 端口 18 19 public SimpleChatClient(String host, int port) { 20 this.host = host; 21 this.port = port; 22 } 23 24 // 配置並運行客戶端 25 public void run() throws Exception { 26 EventLoopGroup group = new NioEventLoopGroup(); 27 try { 28 Bootstrap b = new Bootstrap(); // 客戶端輔助啟動類 29 b.group(group) // 客戶端只需要一個用來接收並處理連接 30 .channel(NioSocketChannel.class) // 設置如何接受連接 31 .handler(new SimpleChatClientInitializer());// 配置 channel 32 // 連接服務器 33 Channel channel = b.connect(host, port).sync().channel(); 34 // 讀取控制台輸入字符 35 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 36 while(true) { 37 // 每行成一幀輸出,以"\r\n"結尾 38 channel.writeAndFlush(in.readLine() + "\r\n"); 39 } 40 } catch (Exception e) { 41 e.printStackTrace(); // 輸出異常 42 } finally { 43 group.shutdownGracefully(); // 關閉 44 } 45 } 46 47 public static void main(String[] args) throws Exception { 48 new SimpleChatClient("localhost", 8080).run(); // 啟動客戶端 49 } 50 51 }
運行結果:
六、總結
通過代碼的形式,我們對netty有了直觀的了解和實際上的掌握。