<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Netty | 工作流程圖分析 & 核心組件說明 & 代碼案例實(shí)踐

          共 14745字,需瀏覽 30分鐘

           ·

          2021-10-16 20:17

          一、Netty 工作流程

          我們先來看看Netty的工作原理圖,簡單說一下工作流程,然后通過這張圖來一一分析Netty的核心組件。

          1.1、Server工作流程圖:

          1.2、Server工作流程分析:

          1. server端啟動(dòng)時(shí)綁定本地某個(gè)端口,初始化NioServerSocketChannel.

          2. 將自己NioServerSocketChannel注冊(cè)到某個(gè)BossNioEventLoopGroup的selector上。

            • server端包含1個(gè)Boss NioEventLoopGroup和1個(gè)Worker NioEventLoopGroup,

            • Boss NioEventLoopGroup專門負(fù)責(zé)接收客戶端的連接,Worker NioEventLoopGroup專門負(fù)責(zé)網(wǎng)絡(luò)的讀寫

            • NioEventLoopGroup相當(dāng)于1個(gè)事件循環(huán)組,這個(gè)組里包含多個(gè)事件循環(huán)NioEventLoop,每個(gè)NioEventLoop包含1個(gè)selector和1個(gè)事件循環(huán)線程。

          3. BossNioEventLoopGroup循環(huán)執(zhí)行的任務(wù):

            1、輪詢accept事件;

            2、處理accept事件,將生成的NioSocketChannel注冊(cè)到某一個(gè)WorkNioEventLoopGroup的Selector上。

            3、處理任務(wù)隊(duì)列中的任務(wù),runAllTasks。任務(wù)隊(duì)列中的任務(wù)包括用戶調(diào)用eventloop.execute或schedule執(zhí)行的任務(wù),或者其它線程提交到該eventloop的任務(wù)。

          4. WorkNioEventLoopGroup循環(huán)執(zhí)行的任務(wù):

            • 輪詢read和Write事件

            • 處理IO事件,在NioSocketChannel可讀、可寫事件發(fā)生時(shí),回調(diào)(觸發(fā))ChannelHandler進(jìn)行處理。

            • 處理任務(wù)隊(duì)列的任務(wù),即 runAllTasks

          1.3、Client工作流程圖

          流程就不重復(fù)概述啦??

          二、核心模塊組件

          Netty的核心組件大致是以下幾個(gè):

          1. Channel 接口

          2. EventLoopGroup 接口

          3. ChannelFuture 接口

          4. ChannelHandler 接口

          5. ChannelPipeline 接口

          6. ChannelHandlerContext 接口

          7. SimpleChannelInboundHandler 抽象類

          8. Bootstrap、ServerBootstrap 類

          9. ChannelFuture 接口

          10. ChannelOption 類

          2.1、Channel 接口

          我們平常用到基本的 I/O 操作(bind()、connect()、read()和 write()),其本質(zhì)都依賴于底層網(wǎng)絡(luò)傳輸所提供的原語,在Java中就是Socket類。

          Netty 的 Channel 接 口所提供的 API,大大地降低了直接使用Socket 類的復(fù)雜性。另外Channel 提供異步的網(wǎng)絡(luò) I/O 操作(如建立連接,讀寫,綁定端口),異步調(diào)用意味著任何 I/O 調(diào)用都將立即返回,并且不保證在調(diào)用結(jié)束時(shí)所請(qǐng)求的 I/O 操作已完成。

          在調(diào)用結(jié)束后立即返回一個(gè) ChannelFuture 實(shí)例,通過注冊(cè)監(jiān)聽器到 ChannelFuture 上,支持 在I/O 操作成功、失敗或取消時(shí)立馬回調(diào)通知調(diào)用方。

          此外,Channel 也是擁有許多預(yù)定義的、專門化實(shí)現(xiàn)的廣泛類層次結(jié)構(gòu)的根,比如:

          • LocalServerChannel:用于本地傳輸?shù)腟erverChannel ,允許 VM 通信。

          • EmbeddedChannel:以嵌入式方式使用的 Channel 實(shí)現(xiàn)的基類。

          • NioSocketChannel:異步的客戶端 ?TCP 、Socket 連接。

          • NioServerSocketChannel:異步的服務(wù)器端 ?TCP、Socket 連接。

          • NioDatagramChannel:異步的 ?UDP 連接。

          • NioSctpChannel:異步的客戶端 Sctp 連接,它使用非阻塞模式并允許將 SctpMessage 讀/寫到底層 SctpChannel。

          • NioSctpServerChannel:異步的 Sctp 服務(wù)器端連接,這些通道涵蓋了 UDP 和 TCP 網(wǎng)絡(luò) IO 以及文件 IO。

          2.2、EventLoopGroup接口

          EventLoop 定義了Netty的核心抽象,用于處理連接的生命周期中所發(fā)生的事件。

          Netty 通過觸發(fā)事件將 Selector 從應(yīng)用程序中抽象出來,消除了所有本來將需要手動(dòng)編寫 的派發(fā)代碼。在內(nèi)部,將會(huì)為每個(gè) Channel 分配一個(gè) EventLoop,用以處理所有事件,包括:

          • 注冊(cè)事件;

          • 將事件派發(fā)給 ChannelHandler;

          • 安排進(jìn)一步的動(dòng)作。

          不過在這里我們不深究它,針對(duì) Channel、EventLoop、Thread 以及 EventLoopGroup 之間的關(guān)系做一個(gè)簡單說明。

          • 一個(gè)EventLoopGroup 包含一個(gè)或者多個(gè) EventLoop

          • 每個(gè) EventLoop 維護(hù)著一個(gè) Selector 實(shí)例,所以一個(gè) EventLoop 在它的生命周期內(nèi)只和一個(gè) Thread 綁定;

          • 因此所有由 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理,實(shí)際上消除了對(duì)于同步的需要;

          • 一個(gè) Channel 在它的生命周期內(nèi)只注冊(cè)于一個(gè) EventLoop;

          • 一個(gè) EventLoop 可能會(huì)被分配給一個(gè)或多個(gè)Channel。

          • 通常一個(gè)服務(wù)端口即一個(gè) ServerSocketChannel 對(duì)應(yīng)一個(gè) Selector 和一個(gè) EventLoop 線程。BossEventLoop 負(fù)責(zé)接收客戶端的連接并將 SocketChannel 交給 WorkerEventLoopGroup 來進(jìn)行 IO 處理,就如上文中的流程圖一樣。

          2.3、ChannelFuture 接口

          Netty 中所有的 I/O 操作都是異步的。因?yàn)橐粋€(gè)操作可能不會(huì) 立即返回,所以我們需要一種用于在之后的某個(gè)時(shí)間點(diǎn)確定其結(jié)果的方法。具體的實(shí)現(xiàn)就是通過 FutureChannelFutures,其 addListener()方法注冊(cè)了一個(gè) ChannelFutureListener,以便在某個(gè)操作完成時(shí)(無論是否成功)自動(dòng)觸發(fā)注冊(cè)的監(jiān)聽事件。

          常見的方法有

          • Channel channel(),返回當(dāng)前正在進(jìn)行 IO 操作的通道

          • ChannelFuture sync(),等待異步操作執(zhí)行完畢

          2.4、ChannelHandler 接口

          從之前的入門程序中,我們可以看到ChannelHandler在Netty中的重要性,它充當(dāng)了所有處理入站和出站數(shù)據(jù)的應(yīng)用程序邏輯的容器。我們的業(yè)務(wù)邏輯也大都寫在實(shí)現(xiàn)的字類中,另外ChannelHandler 的方法是由事件自動(dòng)觸發(fā)的,并不需要我們自己派發(fā)。

          ChannelHandler的實(shí)現(xiàn)類或者實(shí)現(xiàn)子接口有很多。平時(shí)我們就是去繼承或子接口,然后重寫里面的方法。

          最常見的幾種Handler:

          • ChannelInboundHandler :接收入站事件和數(shù)據(jù)

          • ChannelOutboundHandler:用于處理出站事件和數(shù)據(jù)。

          常見的適配器:

          • ChannelInboundHandlerAdapter:用于處理入站IO事件

            ChannelInboundHandler實(shí)現(xiàn)的抽象基類,它提供了所有方法的實(shí)現(xiàn)。這個(gè)實(shí)現(xiàn)只是將操作轉(zhuǎn)發(fā)到ChannelPipeline的下一個(gè)ChannelHandler 。子類可以覆蓋方法實(shí)現(xiàn)來改變這一

          • ChannelOutboundHandlerAdapter:用于處理出站IO事件

          我們經(jīng)常需要自定義一個(gè) Handler 類去繼承 ChannelInboundHandlerAdapter,然后通過重寫相應(yīng)方法實(shí)現(xiàn)業(yè)務(wù)邏輯,我們來看看有哪些方法可以重寫:

          public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
          //注冊(cè)事件
          public void channelRegistered(ChannelHandlerContext ctx) ;
          //
          public void channelUnregistered(ChannelHandlerContext ctx);
          //通道已經(jīng)就緒
          public void channelActive(ChannelHandlerContext ctx);

          public void channelInactive(ChannelHandlerContext ctx) ;
          //通道讀取數(shù)據(jù)事件
          public void channelRead(ChannelHandlerContext ctx, Object msg) ;
          //通道讀取數(shù)據(jù)事件完畢
          public void channelReadComplete(ChannelHandlerContext ctx) ;

          public void userEventTriggered(ChannelHandlerContext ctx, Object evt);
          //通道可寫性已更改
          public void channelWritabilityChanged(ChannelHandlerContext ctx);
          //異常處理
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
          }
          復(fù)制代碼

          2.5、ChannelPipeline 接口

          ChannelPipeline 提供了 ChannelHandler 鏈的容器,并定義了用于在該鏈上傳播入站和出站事件流的 API。當(dāng) Channel 被創(chuàng)建時(shí),它會(huì)被自動(dòng)地分配到它專屬的 ChannelPipeline。他們的組成關(guān)系如下:

          一個(gè) Channel 包含了一個(gè) ChannelPipeline,而 ChannelPipeline 中又維護(hù)了一個(gè)由ChannelHandlerContext 組成的雙向鏈表,并且每個(gè)ChanneHandlerContext中又關(guān)聯(lián)著一個(gè)ChannelHandler

          ChannelHandler 安裝到 ChannelPipeline 中的過程:

          1. 一個(gè)ChannelInitializer的實(shí)現(xiàn)被注冊(cè)到了ServerBootstrap中 ;

          2. 當(dāng) ChannelInitializer.initChannel()方法被調(diào)用時(shí),ChannelInitializer將在 ChannelPipeline 中安裝一組自定義的 ChannelHandler;

          3. ChannelInitializer 將它自己從 ChannelPipeline 中移除。

          從一個(gè)客戶端應(yīng)用程序 的角度來看,如果事件的運(yùn)動(dòng)方向是從客戶端到服務(wù)器端,那么我們稱這些事件為出站的,反之 則稱為入站的。服務(wù)端反之。

          如果一個(gè)消息或者任何其他的入站事件被讀取,那么它會(huì)從 ChannelPipeline 的頭部 開始流動(dòng),并被傳遞給第一個(gè) ChannelInboundHandler。次此handler處理完后,數(shù)據(jù)將會(huì)被傳遞給鏈中的下一個(gè) ChannelInboundHandler。最終,數(shù)據(jù)將會(huì)到達(dá) ChannelPipeline 的尾端,至此,所有處理就結(jié)束了。

          出站事件會(huì)從尾端往前傳遞到最前一個(gè)出站的 handler。出站和入站兩種類型的 handler互不干擾。

          2.6、ChannelHandlerContext 接口

          作用就是使ChannelHandler能夠與其ChannelPipeline和其他處理程序交互。因?yàn)?ChannelHandlerContext保存channel相關(guān)的所有上下文信息,同時(shí)關(guān)聯(lián)一個(gè) ChannelHandler 對(duì)象, 另外,ChannelHandlerContext 可以通知ChannelPipeline的下一個(gè)ChannelHandler以及動(dòng)態(tài)修改它所屬的ChannelPipeline

          2.7、SimpleChannelInboundHandler 抽象類

          我們常常能夠遇到應(yīng)用程序會(huì)利用一個(gè) ?ChannelHandler 來接收解碼消息,并在這個(gè)Handler中實(shí)現(xiàn)業(yè)務(wù)邏輯,要寫一個(gè)這樣的 ChannelHandler ,我們只需要擴(kuò)展抽象類 SimpleChannelInboundHandler< T > 即可, 其中T類型是我們要處理的消息的Java類型。

          SimpleChannelInboundHandler 中最重要的方法就是void channelRead0(ChannelHandlerContext ctx, T msg),

          我們自己實(shí)現(xiàn)了這個(gè)方法之后,接收到的消息就已經(jīng)被解碼完的消息啦。

          舉個(gè)例子:

          2.8、Bootstrap、ServerBootstrap 類

          Bootstrap 意思是引導(dǎo),一個(gè) Netty 應(yīng)用通常由一個(gè) Bootstrap 開始,主要作用是配置整個(gè) Netty 程序,串聯(lián)各個(gè)組件。

          類別BootstrapServerBootstrap
          引導(dǎo)用于引導(dǎo)客戶端用于引導(dǎo)服務(wù)端
          在網(wǎng)絡(luò)編程中作用用于連接到遠(yuǎn)程主機(jī)和端口用于綁定到一個(gè)本地端口
          EventLoopGroup 的數(shù)目12

          我想大家對(duì)于最后一點(diǎn)可能會(huì)存有疑惑,為什么一個(gè)是1一個(gè)是2呢?

          因?yàn)榉?wù)器需要兩組不同的 Channel

          第一組將只包含一個(gè) ServerChannel,代表服務(wù) 器自身的已綁定到某個(gè)本地端口的正在監(jiān)聽的套接字。

          而第二組將包含所有已創(chuàng)建的用來處理傳入客戶端連接(對(duì)于每個(gè)服務(wù)器已經(jīng)接受的連接都有一個(gè))的 Channel

          這一點(diǎn)可以上文中的流程圖。

          2.9、ChannelFuture ?接口

          異步 Channel I/O 操作的結(jié)果。Netty 中的所有 I/O 操作都是異步的。這意味著任何 I/O 調(diào)用將立即返回,但不保證在調(diào)用結(jié)束時(shí)請(qǐng)求的 I/O 操作已完成。相反,您將返回一個(gè) ChannelFuture 實(shí)例,該實(shí)例為您提供有關(guān) I/O 操作的結(jié)果或狀態(tài)的信息。ChannelFuture 要么未完成,要么已完成。當(dāng) I/O 操作開始時(shí),會(huì)創(chuàng)建一個(gè)新的未來對(duì)象。新的未來最初是未完成的——它既沒有成功,也沒有失敗,也沒有取消,因?yàn)?I/O 操作還沒有完成。如果 I/O 操作成功完成、失敗或取消,則使用更具體的信息(例如失敗原因)將未來標(biāo)記為已完成。請(qǐng)注意,即使失敗和取消也屬于完成狀態(tài)。

          Netty 中所有的 IO 操作都是異步的,不能立刻得知消息是否被正確處理。但是可以過一會(huì)等它執(zhí)行完成或者直接注冊(cè)一個(gè)監(jiān)聽,具體的實(shí)現(xiàn)就是通過 FutureChannelFutures,他們可以注冊(cè)一個(gè)監(jiān)聽,當(dāng)操作執(zhí)行成功或失敗時(shí)監(jiān)聽會(huì)自動(dòng)觸發(fā)注冊(cè)的監(jiān)聽事件

          常見的方法有

          • Channel channel(),返回當(dāng)前正在進(jìn)行 IO 操作的通道

          • ChannelFuture sync(),等待異步操作執(zhí)行完畢

          2.10、ChannelOption 類

          1. Netty 在創(chuàng)建 Channel 實(shí)例后,一般都需要設(shè)置 ChannelOption 參數(shù)。

          2. ChannelOption 參數(shù)如下:

            • ChannelOption.SO_KEEPALIVE :一直保持連接狀態(tài)

            • ChannelOption.SO_BACKLOG:對(duì)應(yīng)TCP/IP協(xié)議listen 函數(shù)中的backlog參數(shù),用來初始化服務(wù)器可連接隊(duì)列大小。服務(wù)端處理客戶端連接請(qǐng)求是順序處理內(nèi),所N博求放在隊(duì)剛中等待處理,backilog參數(shù)指定端來的時(shí)候,服務(wù)端將不能處理的客戶端連接請(qǐng)求放在隊(duì)列中等待處理, backlog參數(shù)指定了隊(duì)列的大小。

          三、應(yīng)用實(shí)例

          【案例】:

          寫一個(gè)服務(wù)端,兩個(gè)或多個(gè)客戶端,客戶端可以相互通信。

          3.1、服務(wù)端 Handler

          ChannelHandler的實(shí)現(xiàn)類或者實(shí)現(xiàn)子接口有很多。平時(shí)我們就是去繼承或子接口,然后重寫里面的方法。

          在這里我們就是繼承了 SimpleChannelInboundHandler< T > ,這里面許多方法都是大都只要我們重寫一下業(yè)務(wù)邏輯,觸發(fā)大都是在事件發(fā)生時(shí)自動(dòng)調(diào)用的,無需我們手動(dòng)調(diào)用。

          package com.crush.atguigu.group_chat;

          import io.netty.channel.Channel;
          import io.netty.channel.ChannelHandlerContext;
          import io.netty.channel.SimpleChannelInboundHandler;
          import io.netty.channel.group.ChannelGroup;
          import io.netty.channel.group.DefaultChannelGroup;
          import io.netty.util.concurrent.GlobalEventExecutor;

          import java.text.SimpleDateFormat;
          import java.util.ArrayList;
          import java.util.HashMap;
          import java.util.List;
          import java.util.Map;

          /**
          * @author crush
          */

          public class GroupChatServerHandler extends SimpleChannelInboundHandler<String> {

          /**
          * 定義一個(gè)channle 組,管理所有的channel
          * GlobalEventExecutor.INSTANCE) 是全局的事件執(zhí)行器,是一個(gè)單例
          */

          private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


          /**
          * handlerAdded 表示連接建立,一旦連接,第一個(gè)被執(zhí)行
          * 將當(dāng)前channel 加入到 channelGroup
          * @param ctx
          * @throws Exception
          */

          @Override
          public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
          Channel channel = ctx.channel();
          //將該客戶加入聊天的信息推送給其它在線的客戶端
          /*
          該方法會(huì)將 channelGroup 中所有的channel 遍歷,并發(fā)送 消息,
          我們不需要自己遍歷
          */

          channelGroup.writeAndFlush("[客戶端]" + channel.remoteAddress() + " 加入聊天" + sdf.format(new java.util.Date()) + " \n");
          channelGroup.add(channel);

          }

          /**
          * 斷開連接, 將xx客戶離開信息推送給當(dāng)前在線的客戶
          * @param ctx
          * @throws Exception
          */

          @Override
          public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

          Channel channel = ctx.channel();
          channelGroup.writeAndFlush("[客戶端]" + channel.remoteAddress() + " 離開了\n");
          System.out.println("channelGroup size" + channelGroup.size());

          }

          /**
          * 表示channel 處于活動(dòng)狀態(tài), 既剛出生 提示 xx上線
          * @param ctx
          * @throws Exception
          */

          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception {

          System.out.println(ctx.channel().remoteAddress() + " 上線了~");
          }

          /**
          * 表示channel 處于不活動(dòng)狀態(tài), 既死亡狀態(tài) 提示 xx離線了
          * @param ctx
          * @throws Exception
          */

          @Override
          public void channelInactive(ChannelHandlerContext ctx) throws Exception {

          System.out.println(ctx.channel().remoteAddress() + " 離線了~");
          }

          //讀取數(shù)據(jù)
          @Override
          protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {

          //獲取到當(dāng)前channel
          Channel channel = ctx.channel();
          //這時(shí)我們遍歷channelGroup, 根據(jù)不同的情況,回送不同的消息

          channelGroup.forEach(ch -> {
          if (channel != ch) { //不是當(dāng)前的channel,轉(zhuǎn)發(fā)消息
          ch.writeAndFlush("[客戶]" + channel.remoteAddress() + " 發(fā)送了消息" + msg + "\n");
          } else {//回顯自己發(fā)送的消息給自己
          ch.writeAndFlush("[自己]發(fā)送了消息" + msg + "\n");
          }
          });
          }

          @Override
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
          //關(guān)閉通道
          ctx.close();
          }
          }
          復(fù)制代碼

          3.2、服務(wù)端 Server 啟動(dòng)

          package com.crush.atguigu.group_chat;

          import io.netty.bootstrap.ServerBootstrap;
          import io.netty.channel.*;
          import io.netty.channel.nio.NioEventLoopGroup;
          import io.netty.channel.socket.SocketChannel;
          import io.netty.channel.socket.nio.NioServerSocketChannel;
          import io.netty.handler.codec.string.StringDecoder;
          import io.netty.handler.codec.string.StringEncoder;

          /**
          * @author crush
          */

          public class GroupChatServer {

          /**
          * //監(jiān)聽端口
          */

          private int port;

          public GroupChatServer(int port) {
          this.port = port;
          }

          /**
          * 編寫run方法 處理請(qǐng)求
          * @throws Exception
          */

          public void run() throws Exception {

          //創(chuàng)建兩個(gè)線程組
          EventLoopGroup bossGroup = new NioEventLoopGroup(1);
          //8個(gè)NioEventLoop
          EventLoopGroup workerGroup = new NioEventLoopGroup();

          try {
          ServerBootstrap b = new ServerBootstrap();

          b.group(bossGroup, workerGroup)
          .channel(NioServerSocketChannel.class)
          .option(ChannelOption.SO_BACKLOG, 128)
          .childOption(ChannelOption.SO_KEEPALIVE, true)
          .childHandler(new ChannelInitializer() {
          @Override
          protected void initChannel(SocketChannel ch) throws Exception {
          //獲取到pipeline
          ChannelPipeline pipeline = ch.pipeline();
          //向pipeline加入解碼器
          pipeline.addLast("decoder", new StringDecoder());
          //向pipeline加入編碼器
          pipeline.addLast("encoder", new StringEncoder());
          //加入自己的業(yè)務(wù)處理handler
          pipeline.addLast(new GroupChatServerHandler());
          }
          });

          System.out.println("netty 服務(wù)器啟動(dòng)");

          ChannelFuture channelFuture = b.bind(port).sync();

          //監(jiān)聽關(guān)閉
          channelFuture.channel().closeFuture().sync();
          } finally {
          bossGroup.shutdownGracefully();
          workerGroup.shutdownGracefully();
          }
          }

          public static void main(String[] args) throws Exception {
          new GroupChatServer(7000).run();
          }
          }
          復(fù)制代碼

          3.3、客戶端 Handler

          package com.crush.atguigu.group_chat;

          import io.netty.channel.ChannelHandlerContext;
          import io.netty.channel.SimpleChannelInboundHandler;
          /**
          * @author crush
          */

          public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {

          //當(dāng)前Channel 已從對(duì)方讀取消息時(shí)調(diào)用。
          @Override
          protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
          System.out.println(msg.trim());
          }
          }
          復(fù)制代碼

          3.4、客戶端 Server

          package com.crush.atguigu.group_chat;

          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 io.netty.handler.codec.string.StringDecoder;
          import io.netty.handler.codec.string.StringEncoder;

          import java.util.Scanner;


          /**
          * @author crush
          */

          public class GroupChatClient {

          private final String host;
          private final int port;

          public GroupChatClient(String host, int port) {
          this.host = host;
          this.port = port;
          }

          public void run() throws Exception {
          EventLoopGroup group = new NioEventLoopGroup();

          try {

          Bootstrap bootstrap = new Bootstrap()
          .group(group)
          .channel(NioSocketChannel.class)
          .handler(new ChannelInitializer() {

          @Override
          protected void initChannel(SocketChannel ch) throws Exception {

          //得到pipeline
          ChannelPipeline pipeline = ch.pipeline();
          //加入相關(guān)handler
          pipeline.addLast("decoder", new StringDecoder());
          pipeline.addLast("encoder", new StringEncoder());
          //加入自定義的handler
          pipeline.addLast(new GroupChatClientHandler());
          }
          });

          ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
          //得到channel
          Channel channel = channelFuture.channel();
          System.out.println("-------" + channel.localAddress() + "--------");
          //客戶端需要輸入信息
          Scanner scanner = new Scanner(System.in);
          while (scanner.hasNextLine()) {
          String msg = scanner.nextLine();
          //通過channel 發(fā)送到服務(wù)器端
          channel.writeAndFlush(msg + "\r\n");
          }
          } finally {
          group.shutdownGracefully();
          }
          }

          public static void main(String[] args) throws Exception {
          new GroupChatClient("127.0.0.1", 7000).run();
          }
          }
          復(fù)制代碼

          多個(gè)客戶端,cv一下即可。

          3.5、測試:

          測試流程是先啟動(dòng) 服務(wù)端 Server,再啟動(dòng)客戶端 。

          四、自言自語

          這篇文章應(yīng)該算是個(gè)存稿了,之前忙其他事情去了??。


          作者:寧在春
          鏈接:https://juejin.cn/post/7017602386747195429
          來源:稀土掘金
          著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。



          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  少妇大战28厘米黑人 | 亚洲国产免费视频 | 久久久久久99精品久久久 | 日本中文无码视频 | 国产伦精品一区二区三区妓女 |