<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>

          SpringBoot整合Netty并使用Protobuf進(jìn)行數(shù)據(jù)傳輸 (附工程)

          共 12419字,需瀏覽 25分鐘

           ·

          2020-09-20 14:30

          前言

          本篇文章主要介紹的是SpringBoot整合Netty以及使用Protobuf進(jìn)行數(shù)據(jù)傳輸?shù)南嚓P(guān)內(nèi)容。Protobuf會(huì)簡(jiǎn)單的介紹下用法,至于Netty在之前的文章中已經(jīng)簡(jiǎn)單的介紹過(guò)了,這里就不再過(guò)多細(xì)說(shuō)了。

          Protobuf

          介紹

          protocolbuffer(以下簡(jiǎn)稱(chēng)PB)是google 的一種數(shù)據(jù)交換的格式,它獨(dú)立于語(yǔ)言,獨(dú)立于平臺(tái)。google 提供了多種語(yǔ)言的實(shí)現(xiàn):java、c#、c++、go 和python,每一種實(shí)現(xiàn)都包含了相應(yīng)語(yǔ)言的編譯器以及庫(kù)文件。由于它是一種二進(jìn)制的格式,比使用 xml進(jìn)行數(shù)據(jù)交換快許多。可以把它用于分布式應(yīng)用之間的數(shù)據(jù)通信或者異構(gòu)環(huán)境下的數(shù)據(jù)交換。作為一種效率和兼容性都很優(yōu)秀的二進(jìn)制數(shù)據(jù)傳輸格式,可以用于諸如網(wǎng)絡(luò)傳輸、配置文件、數(shù)據(jù)存儲(chǔ)等諸多領(lǐng)域。

          官方地址:?https://github.com/google/protobuf

          使用

          這里的使用就只介紹Java相關(guān)的使用。
          首先我們需要建立一個(gè)proto文件,在該文件定義我們需要傳輸?shù)奈募?br>例如我們需要定義一個(gè)用戶的信息,包含的字段主要有編號(hào)、名稱(chēng)、年齡。
          那么該protobuf文件的格式如下:
          :這里使用的是proto3,相關(guān)的注釋我已寫(xiě)了,這里便不再過(guò)多講述了。需要注意一點(diǎn)的是proto文件和生成的Java文件名稱(chēng)不能一致!

          syntax = "proto3";
          // 生成的包名
          option java_package="com.pancm.protobuf";
          //生成的java名
          option java_outer_classname = "UserInfo";

          message UserMsg {

          // ID
          int32 id = 1;

          // 姓名
          string name = 2;

          // 年齡
          int32 age = 3;

          // 狀態(tài)
          int32 state = 4;
          }

          創(chuàng)建好該文件之后,我們把該文件和protoc.exe(生成Java文件的軟件)放到E盤(pán)目錄下的protobuf文件夾下,然后再到該目錄的dos界面下輸入:protoc.exe --java_out=文件絕對(duì)路徑名稱(chēng)
          例如:

          protoc.exe --java_out=E:\protobuf User.proto

          輸入完之后,回車(chē)即可在同級(jí)目錄看到已經(jīng)生成好的Java文件,然后將該文件放到項(xiàng)目中該文件指定的路徑下即可。

          注:生成protobuf的文件軟件和測(cè)試的protobuf文件我也整合到該項(xiàng)目中了,可以直接獲取的。

          Java文件生成好之后,我們?cè)賮?lái)看怎么使用。
          這里我就直接貼代碼了,并且將注釋寫(xiě)在代碼中,應(yīng)該更容易理解些吧。。。
          代碼示例:

          	 // 按照定義的數(shù)據(jù)結(jié)構(gòu),創(chuàng)建一個(gè)對(duì)象
          UserInfo.UserMsg.Builder userInfo = UserInfo.UserMsg.newBuilder();
          userInfo.setId(1);
          userInfo.setName("xuwujing");
          userInfo.setAge(18);
          UserInfo.UserMsg userMsg = userInfo.build();
          // 將數(shù)據(jù)寫(xiě)到輸出流
          ByteArrayOutputStream output = new ByteArrayOutputStream();
          userMsg.writeTo(output);
          // 將數(shù)據(jù)序列化后發(fā)送
          byte[] byteArray = output.toByteArray();
          // 接收到流并讀取
          ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
          // 反序列化
          UserInfo.UserMsg userInfo2 = UserInfo.UserMsg.parseFrom(input);
          System.out.println("id:" + userInfo2.getId());
          System.out.println("name:" + userInfo2.getName());
          System.out.println("age:" + userInfo2.getAge());

          注:這里說(shuō)明一點(diǎn),因?yàn)?strong>protobuf是通過(guò)二進(jìn)制進(jìn)行傳輸,所以需要注意下相應(yīng)的編碼。還有使用protobuf也需要注意一下一次傳輸?shù)淖畲笞止?jié)長(zhǎng)度。

          輸出結(jié)果:

          id:1
          name:xuwujing
          age:18

          SpringBoot整合Netty

          說(shuō)明:如果想直接獲取工程那么可以直接跳到底部,通過(guò)鏈接下載工程代碼。

          開(kāi)發(fā)準(zhǔn)備

          環(huán)境要求
          JDK::1.8
          Netty::4.0或以上(不包括5)
          Protobuf:3.0或以上

          如果對(duì)Netty不熟的話,可以看看我之前寫(xiě)的一些文章。大神請(qǐng)無(wú)視~。~
          地址:https://blog.csdn.net/column/details/17640.html

          首先還是Maven的相關(guān)依賴(lài):

          <properties>
          <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
          <java.version>1.8java.version>
          <netty.version>4.1.22.Finalnetty.version>
          <protobuf.version>3.5.1protobuf.version>
          <springboot>1.5.9.RELEASEspringboot>
          <fastjson>1.2.41fastjson>
          <maven.compiler.source>1.8maven.compiler.source>
          <maven.compiler.target>1.8maven.compiler.target>
          properties>


          <dependencies>

          <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starterartifactId>
          <version>${springboot}version>
          dependency>


          <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-testartifactId>
          <version>${springboot}version>
          <scope>testscope>
          dependency>

          <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-devtoolsartifactId>
          <version>${springboot}version>
          <optional>trueoptional>
          dependency>


          <dependency>
          <groupId>io.nettygroupId>
          <artifactId>netty-allartifactId>
          <version>${netty.version}version>
          dependency>

          <dependency>
          <groupId>com.google.protobufgroupId>
          <artifactId>protobuf-javaartifactId>
          <version>${protobuf.version}version>
          dependency>

          <dependency>
          <groupId>com.alibabagroupId>
          <artifactId>fastjsonartifactId>
          <version>${fastjson}version>
          dependency>


          <dependency>
          <groupId>junitgroupId>
          <artifactId>junitartifactId>
          <version>4.12version>
          <scope>testscope>
          dependency>
          dependencies>

          添加了相應(yīng)的maven依賴(lài)之后,配置文件這塊暫時(shí)沒(méi)有什么可以添加的,因?yàn)闀簳r(shí)就一個(gè)監(jiān)聽(tīng)的端口而已。

          代碼編寫(xiě)

          代碼模塊主要分為服務(wù)端和客戶端。
          主要實(shí)現(xiàn)的業(yè)務(wù)邏輯:
          服務(wù)端啟動(dòng)成功之后,客戶端也啟動(dòng)成功,這時(shí)服務(wù)端會(huì)發(fā)送一條protobuf格式的信息給客戶端,然后客戶端給予相應(yīng)的應(yīng)答??蛻舳伺c服務(wù)端連接成功之后,客戶端每個(gè)一段時(shí)間會(huì)發(fā)送心跳指令給服務(wù)端,告訴服務(wù)端該客戶端還存過(guò)中,如果客戶端沒(méi)有在指定的時(shí)間發(fā)送信息,服務(wù)端會(huì)關(guān)閉與該客戶端的連接。當(dāng)客戶端無(wú)法連接到服務(wù)端之后,會(huì)每隔一段時(shí)間去嘗試重連,只到重連成功!

          服務(wù)端

          首先是編寫(xiě)服務(wù)端的啟動(dòng)類(lèi),相應(yīng)的注釋在代碼中寫(xiě)得很詳細(xì)了,這里也不再過(guò)多講述了。不過(guò)需要注意的是,在之前的我寫(xiě)的Netty文章中,是通過(guò)main方法直接啟動(dòng)服務(wù)端,因此是直接new一個(gè)對(duì)象的。而在和SpringBoot整合之后,我們需要將Netty交給springBoot去管理,所以這里就用了相應(yīng)的注解。
          代碼如下:

          @Service("nettyServer")
          public class NettyServer {
          private static final int port = 9876; // 設(shè)置服務(wù)端端口
          private static EventLoopGroup boss = new NioEventLoopGroup(); // 通過(guò)nio方式來(lái)接收連接和處理連接
          private static EventLoopGroup work = new NioEventLoopGroup(); // 通過(guò)nio方式來(lái)接收連接和處理連接
          private static ServerBootstrap b = new ServerBootstrap();

          @Autowired
          private NettyServerFilter nettyServerFilter;


          public void run() {
          try {
          b.group(boss, work);
          b.channel(NioServerSocketChannel.class);
          b.childHandler(nettyServerFilter); // 設(shè)置過(guò)濾器
          // 服務(wù)器綁定端口監(jiān)聽(tīng)
          ChannelFuture f = b.bind(port).sync();
          System.out.println("服務(wù)端啟動(dòng)成功,端口是:" + port);
          // 監(jiān)聽(tīng)服務(wù)器關(guān)閉監(jiān)聽(tīng)
          f.channel().closeFuture().sync();
          } catch (InterruptedException e) {
          e.printStackTrace();
          } finally {
          // 關(guān)閉EventLoopGroup,釋放掉所有資源包括創(chuàng)建的線程
          work.shutdownGracefully();
          boss.shutdownGracefully();
          }
          }
          }

          服務(wù)端主類(lèi)編寫(xiě)完畢之后,我們?cè)賮?lái)設(shè)置下相應(yīng)的過(guò)濾條件。
          這里需要繼承Netty中ChannelInitializer類(lèi),然后重寫(xiě)initChannel該方法,進(jìn)行添加相應(yīng)的設(shè)置,如心跳超時(shí)設(shè)置,傳輸協(xié)議設(shè)置,以及相應(yīng)的業(yè)務(wù)實(shí)現(xiàn)類(lèi)。
          代碼如下:

          	@Component
          public class NettyServerFilter extends ChannelInitializer {

          @Autowired
          private NettyServerHandler nettyServerHandler;

          @Override
          protected void initChannel(SocketChannel ch) throws Exception {
          ChannelPipeline ph = ch.pipeline();

          //入?yún)⒄f(shuō)明: 讀超時(shí)時(shí)間、寫(xiě)超時(shí)時(shí)間、所有類(lèi)型的超時(shí)時(shí)間、時(shí)間格式
          ph.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
          // 解碼和編碼,應(yīng)和客戶端一致
          //傳輸?shù)膮f(xié)議 Protobuf
          ph.addLast(new ProtobufVarint32FrameDecoder());
          ph.addLast(new ProtobufDecoder(UserMsg.getDefaultInstance()));
          ph.addLast(new ProtobufVarint32LengthFieldPrepender());
          ph.addLast(new ProtobufEncoder());

          //業(yè)務(wù)邏輯實(shí)現(xiàn)類(lèi)
          ph.addLast("nettyServerHandler", nettyServerHandler);
          }
          }

          服務(wù)相關(guān)的設(shè)置的代碼寫(xiě)完之后,我們?cè)賮?lái)編寫(xiě)主要的業(yè)務(wù)代碼。
          使用Netty編寫(xiě)業(yè)務(wù)層的代碼,我們需要繼承ChannelInboundHandlerAdapter?或SimpleChannelInboundHandler類(lèi),在這里順便說(shuō)下它們兩的區(qū)別吧。
          繼承SimpleChannelInboundHandler類(lèi)之后,會(huì)在接收到數(shù)據(jù)后會(huì)自動(dòng)release掉數(shù)據(jù)占用的Bytebuffer資源。并且繼承該類(lèi)需要指定數(shù)據(jù)格式。
          而繼承ChannelInboundHandlerAdapter則不會(huì)自動(dòng)釋放,需要手動(dòng)調(diào)用ReferenceCountUtil.release()等方法進(jìn)行釋放。繼承該類(lèi)不需要指定數(shù)據(jù)格式。
          所以在這里,個(gè)人推薦服務(wù)端繼承ChannelInboundHandlerAdapter,手動(dòng)進(jìn)行釋放,防止數(shù)據(jù)未處理完就自動(dòng)釋放了。而且服務(wù)端可能有多個(gè)客戶端進(jìn)行連接,并且每一個(gè)客戶端請(qǐng)求的數(shù)據(jù)格式都不一致,這時(shí)便可以進(jìn)行相應(yīng)的處理。
          客戶端根據(jù)情況可以繼承SimpleChannelInboundHandler類(lèi)。好處是直接指定好傳輸?shù)臄?shù)據(jù)格式,就不需要再進(jìn)行格式的轉(zhuǎn)換了。

          代碼如下:

          @Service("nettyServerHandler")
          public class NettyServerHandler extends ChannelInboundHandlerAdapter {

          /** 空閑次數(shù) */
          private int idle_count = 1;
          /** 發(fā)送次數(shù) */
          private int count = 1;


          /**
          * 建立連接時(shí),發(fā)送一條消息
          */

          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception
          {
          System.out.println("連接的客戶端地址:" + ctx.channel().remoteAddress());
          UserInfo.UserMsg userMsg = UserInfo.UserMsg.newBuilder().setId(1).setAge(18).setName("xuwujing").setState(0)
          .build();
          ctx.writeAndFlush(userMsg);
          super.channelActive(ctx);
          }

          /**
          * 超時(shí)處理 如果5秒沒(méi)有接受客戶端的心跳,就觸發(fā); 如果超過(guò)兩次,則直接關(guān)閉;
          */

          @Override
          public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception
          {
          if (obj instanceof IdleStateEvent) {
          IdleStateEvent event = (IdleStateEvent) obj;
          if (IdleState.READER_IDLE.equals(event.state())) { // 如果讀通道處于空閑狀態(tài),說(shuō)明沒(méi)有接收到心跳命令
          System.out.println("已經(jīng)5秒沒(méi)有接收到客戶端的信息了");
          if (idle_count > 1) {
          System.out.println("關(guān)閉這個(gè)不活躍的channel");
          ctx.channel().close();
          }
          idle_count++;
          }
          } else {
          super.userEventTriggered(ctx, obj);
          }
          }

          /**
          * 業(yè)務(wù)邏輯處理
          */

          @Override
          public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
          {
          System.out.println("第" + count + "次" + ",服務(wù)端接受的消息:" + msg);
          try {
          // 如果是protobuf類(lèi)型的數(shù)據(jù)
          if (msg instanceof UserMsg) {
          UserInfo.UserMsg userState = (UserInfo.UserMsg) msg;
          if (userState.getState() == 1) {
          System.out.println("客戶端業(yè)務(wù)處理成功!");
          } else if(userState.getState() == 2){
          System.out.println("接受到客戶端發(fā)送的心跳!");
          }else{
          System.out.println("未知命令!");
          }
          } else {
          System.out.println("未知數(shù)據(jù)!" + msg);
          return;
          }
          } catch (Exception e) {
          e.printStackTrace();
          } finally {
          ReferenceCountUtil.release(msg);
          }
          count++;
          }

          /**
          * 異常處理
          */

          @Override
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
          {
          cause.printStackTrace();
          ctx.close();
          }
          }

          還有個(gè)服務(wù)端的啟動(dòng)類(lèi),之前是通過(guò)main方法直接啟動(dòng), 不過(guò)這里改成了通過(guò)springBoot進(jìn)行啟動(dòng),差別不大。
          代碼如下:

          @SpringBootApplication
          public class NettyServerApp {

          public static void main(String[] args) {
          // 啟動(dòng)嵌入式的 Tomcat 并初始化 Spring 環(huán)境及其各 Spring 組件
          ApplicationContext context = SpringApplication.run(NettyServerApp.class, args);
          NettyServer nettyServer = context.getBean(NettyServer.class);
          nettyServer.run();
          }

          }

          到這里服務(wù)端相應(yīng)的代碼就編寫(xiě)完畢了。

          客戶端

          客戶端這邊的代碼和服務(wù)端的很多地方都類(lèi)似,我就不再過(guò)多細(xì)說(shuō)了,主要將一些不同的代碼拿出來(lái)簡(jiǎn)單的講述下。
          首先是客戶端的主類(lèi),基本和服務(wù)端的差不多,也就是多了監(jiān)聽(tīng)的端口和一個(gè)監(jiān)聽(tīng)器(用來(lái)監(jiān)聽(tīng)是否和服務(wù)端斷開(kāi)連接,用于重連)。
          主要實(shí)現(xiàn)的代碼邏輯如下:

          	public void doConnect(Bootstrap bootstrap, EventLoopGroup eventLoopGroup) {
          ChannelFuture f = null;
          try {
          if (bootstrap != null) {
          bootstrap.group(eventLoopGroup);
          bootstrap.channel(NioSocketChannel.class);
          bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
          bootstrap.handler(nettyClientFilter);
          bootstrap.remoteAddress(host, port);
          f = bootstrap.connect().addListener((ChannelFuture futureListener) -> {
          final EventLoop eventLoop = futureListener.channel().eventLoop();
          if (!futureListener.isSuccess()) {
          System.out.println("與服務(wù)端斷開(kāi)連接!在10s之后準(zhǔn)備嘗試重連!");
          eventLoop.schedule(() -> doConnect(new Bootstrap(), eventLoop), 10, TimeUnit.SECONDS);
          }
          });
          if(initFalg){
          System.out.println("Netty客戶端啟動(dòng)成功!");
          initFalg=false;
          }
          // 阻塞
          f.channel().closeFuture().sync();
          }
          } catch (Exception e) {
          System.out.println("客戶端連接失敗!"+e.getMessage());
          }
          }

          注:監(jiān)聽(tīng)器這塊的實(shí)現(xiàn)用的是JDK1.8的寫(xiě)法。

          客戶端過(guò)濾其這塊基本和服務(wù)端一直。不過(guò)需要注意的是,傳輸協(xié)議、編碼和解碼應(yīng)該一致,還有心跳的讀寫(xiě)時(shí)間應(yīng)該小于服務(wù)端所設(shè)置的時(shí)間。
          改動(dòng)的代碼如下:

          	ChannelPipeline ph = ch.pipeline();
          /*
          * 解碼和編碼,應(yīng)和服務(wù)端一致
          * */

          //入?yún)⒄f(shuō)明: 讀超時(shí)時(shí)間、寫(xiě)超時(shí)時(shí)間、所有類(lèi)型的超時(shí)時(shí)間、時(shí)間格式
          ph.addLast(new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS));

          客戶端的業(yè)務(wù)代碼邏輯。
          主要實(shí)現(xiàn)的幾點(diǎn)邏輯是心跳按時(shí)發(fā)送以及解析服務(wù)發(fā)送的protobuf格式的數(shù)據(jù)。
          這里比服務(wù)端多個(gè)個(gè)注解, 該注解Sharable主要是為了多個(gè)handler可以被多個(gè)channel安全地共享,也就是保證線程安全。
          廢話就不多說(shuō)了,代碼如下:

          	@Service("nettyClientHandler")
          @ChannelHandler.Sharable
          public class NettyClientHandler extends ChannelInboundHandlerAdapter {
          @Autowired
          private NettyClient nettyClient;

          /** 循環(huán)次數(shù) */
          private int fcount = 1;

          /**
          * 建立連接時(shí)
          */

          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception {
          System.out.println("建立連接時(shí):" + new Date());
          ctx.fireChannelActive();
          }

          /**
          * 關(guān)閉連接時(shí)
          */

          @Override
          public void channelInactive(ChannelHandlerContext ctx) throws Exception {
          System.out.println("關(guān)閉連接時(shí):" + new Date());
          final EventLoop eventLoop = ctx.channel().eventLoop();
          nettyClient.doConnect(new Bootstrap(), eventLoop);
          super.channelInactive(ctx);
          }

          /**
          * 心跳請(qǐng)求處理 每4秒發(fā)送一次心跳請(qǐng)求;
          *
          */

          @Override
          public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
          System.out.println("循環(huán)請(qǐng)求的時(shí)間:" + new Date() + ",次數(shù)" + fcount);
          if (obj instanceof IdleStateEvent) {
          IdleStateEvent event = (IdleStateEvent) obj;
          if (IdleState.WRITER_IDLE.equals(event.state())) { // 如果寫(xiě)通道處于空閑狀態(tài),就發(fā)送心跳命令
          UserMsg.Builder userState = UserMsg.newBuilder().setState(2);
          ctx.channel().writeAndFlush(userState);
          fcount++;
          }
          }
          }

          /**
          * 業(yè)務(wù)邏輯處理
          */

          @Override
          public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
          // 如果不是protobuf類(lèi)型的數(shù)據(jù)
          if (!(msg instanceof UserMsg)) {
          System.out.println("未知數(shù)據(jù)!" + msg);
          return;
          }
          try {

          // 得到protobuf的數(shù)據(jù)
          UserInfo.UserMsg userMsg = (UserInfo.UserMsg) msg;
          // 進(jìn)行相應(yīng)的業(yè)務(wù)處理。。。
          // 這里就從簡(jiǎn)了,只是打印而已
          System.out.println(
          "客戶端接受到的用戶信息。編號(hào):" + userMsg.getId() + ",姓名:" + userMsg.getName() + ",年齡:" + userMsg.getAge());

          // 這里返回一個(gè)已經(jīng)接受到數(shù)據(jù)的狀態(tài)
          UserMsg.Builder userState = UserMsg.newBuilder().setState(1);
          ctx.writeAndFlush(userState);
          System.out.println("成功發(fā)送給服務(wù)端!");
          } catch (Exception e) {
          e.printStackTrace();
          } finally {
          ReferenceCountUtil.release(msg);
          }
          }
          }

          那么到這里客戶端的代碼也編寫(xiě)完畢了。

          功能測(cè)試

          首先啟動(dòng)服務(wù)端,然后再啟動(dòng)客戶端。
          我們來(lái)看看結(jié)果是否如上述所說(shuō)。

          服務(wù)端輸出結(jié)果:

          服務(wù)端啟動(dòng)成功,端口是:9876
          連接的客戶端地址:/127.0.0.1:53319
          1次,服務(wù)端接受的消息:state: 1

          客戶端業(yè)務(wù)處理成功!
          2次,服務(wù)端接受的消息:state: 2

          接受到客戶端發(fā)送的心跳!
          3次,服務(wù)端接受的消息:state: 2

          接受到客戶端發(fā)送的心跳!
          4次,服務(wù)端接受的消息:state: 2

          接受到客戶端發(fā)送的心跳!

          客戶端輸入結(jié)果:

          Netty客戶端啟動(dòng)成功!
          建立連接時(shí):Mon Jul 16 23:31:58 CST 2018
          客戶端接受到的用戶信息。編號(hào):1,姓名:xuwujing,年齡:18
          成功發(fā)送給服務(wù)端!
          循環(huán)請(qǐng)求的時(shí)間:Mon Jul 16 23:32:02 CST 2018,次數(shù)1
          循環(huán)請(qǐng)求的時(shí)間:Mon Jul 16 23:32:06 CST 2018,次數(shù)2
          循環(huán)請(qǐng)求的時(shí)間:Mon Jul 16 23:32:10 CST 2018,次數(shù)3
          循環(huán)請(qǐng)求的時(shí)間:Mon Jul 16 23:32:14 CST 2018,次數(shù)4

          通過(guò)打印信息可以看出如上述所說(shuō)。

          接下來(lái)我們?cè)賮?lái)看看客戶端是否能夠?qū)崿F(xiàn)重連。
          先啟動(dòng)客戶端,再啟動(dòng)服務(wù)端。

          客戶端輸入結(jié)果:

          Netty客戶端啟動(dòng)成功!
          與服務(wù)端斷開(kāi)連接!在10s之后準(zhǔn)備嘗試重連!
          客戶端連接失敗!AbstractChannel$CloseFuture@1fbaa3ac(incomplete)
          建立連接時(shí):Mon Jul 16 23:41:33 CST 2018
          客戶端接受到的用戶信息。編號(hào):1,姓名:xuwujing,年齡:18
          成功發(fā)送給服務(wù)端!
          循環(huán)請(qǐng)求的時(shí)間:Mon Jul 16 23:41:38 CST 2018,次數(shù)1
          循環(huán)請(qǐng)求的時(shí)間:Mon Jul 16 23:41:42 CST 2018,次數(shù)2
          循環(huán)請(qǐng)求的時(shí)間:Mon Jul 16 23:41:46 CST 2018,次數(shù)3

          服務(wù)端輸出結(jié)果:

          服務(wù)端啟動(dòng)成功,端口是:9876
          連接的客戶端地址:/127.0.0.1:53492
          1次,服務(wù)端接受的消息:state: 1

          客戶端業(yè)務(wù)處理成功!
          2次,服務(wù)端接受的消息:state: 2

          接受到客戶端發(fā)送的心跳!
          3次,服務(wù)端接受的消息:state: 2

          接受到客戶端發(fā)送的心跳!
          4次,服務(wù)端接受的消息:state: 2

          結(jié)果也如上述所說(shuō)!

          其它

          關(guān)于SpringBoot整合Netty使用Protobuf進(jìn)行數(shù)據(jù)傳輸?shù)竭@里就結(jié)束了。
          SpringBoot整合Netty使用Protobuf進(jìn)行數(shù)據(jù)傳輸?shù)捻?xiàng)目工程地址:
          https://github.com/xuwujing/springBoot-study/tree/master/springboot-netty-protobuf

          對(duì)了,也有不使用springBoot整合的Netty項(xiàng)目工程地址:
          https://github.com/xuwujing/Netty-study/tree/master/Netty-protobuf


          - 推薦閱讀 -


          架構(gòu)師離職后,成為自由開(kāi)發(fā)者的第 100 天


          下方二維碼關(guān)注我

          互聯(lián)網(wǎng)草根,堅(jiān)持分享技術(shù)、創(chuàng)業(yè)、產(chǎn)品心得和總結(jié)~



          點(diǎn)擊“閱讀原文”,領(lǐng)取 2020 年最新免費(fèi)技術(shù)資料大全

          ↓↓↓
          瀏覽 31
          點(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>
                  婷婷激情视频在线播放 | 看免费的黄色大片 | 无码一区二区三区嫩草网你懂的 | 亚洲一区二区免费视频 | 欧美色图手机在线 |