沉淀再出發:關於netty的一些理解和使用


沉淀再出發:關於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有了直觀的了解和實際上的掌握。

   程序源碼

參考文獻:https://www.cnblogs.com/coderJiebao/tag/netty/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM