Netty整合SpringBoot並使用Protobuf進行資料傳輸

穆書偉發表於2018-10-04

Netty

前言

本篇文章主要介紹的是SpringBoot整合Netty以及使用Protobuf進行資料傳輸的相關內容。Protobuf會介紹下用法,至於Netty在netty 之 telnet HelloWorld 詳解中已經介紹過了,這裡就不再過多細說了。

Protobuf

介紹

Protocol Buffer是Google的語言中立的,平臺中立的,可擴充套件機制的,用於序列化結構化資料 - 對比XML,但更小,更快,更簡單。您可以定義資料的結構化,然後可以使用特殊生成的原始碼輕鬆地在各種資料流中使用各種語言編寫和讀取結構化資料。

官網地址: developers.google.com/protocol-bu…

使用

這裡的使用就只介紹Java相關的使用。具體protobuf3的使用可以看Protobuf 語言指南(proto3)。 首先我們需要在src/main資料夾下建立一個proto資料夾,然後在該資料夾新建一個user.proto檔案,此檔案定義我們需要傳輸的檔案。

Netty整合SpringBoot並使用Protobuf進行資料傳輸

:使用grpc方式編譯**.proto**時,會預設掃描src/main/proto資料夾下的protobuf檔案。

例如我們需要定義一個使用者的資訊,包含的欄位主要有編號、名稱、年齡。 那麼該protobuf檔案的格式如下: :這裡使用的是proto3,相關的註釋我已寫了,這裡便不再過多講述了。需要注意一點的是proto檔案和生成的Java檔名稱不能一致!

//proto3語法註解:如果您不這樣做,protobuf編譯器將假定您正在使用proto2,這必須是檔案的第一個非空的非註釋行。
syntax = "proto3";
//生成的包名
option java_package = "com.sanshengshui.netty.protobuf";
//生成的java名
option java_outer_classname = "UserMsg";

message User{
    //ID
    int32 id = 1;
    //姓名
    string name = 2;
    //年齡
    int32 age = 3;
    //狀態
    int32 state = 4;
}
複製程式碼

建立好該檔案之後,我們cd到該工程的根目錄下,執行mvn clean compile,輸入完之後,回車即可在target資料夾中看到已經生成好的Java檔案,然後直接在工程中使用此protobuf檔案就可以了。因為能自動掃描到此類。詳情請看下圖:

Netty整合SpringBoot並使用Protobuf進行資料傳輸

注:生成protobuf的檔案軟體和測試的protobuf檔案我也整合到該專案中了,可以直接獲取的。

Java檔案生成好之後,我們再來看怎麼使用。 這裡我就直接貼程式碼了,並且將註釋寫在程式碼中,應該更容易理解些吧。。。 程式碼示例:

@RunWith(JUnit4.class)
@Slf4j
public class NettySpringbootProtostuffApplicationTests {
    @Test
    public void ProtobufTest() throws IOException {
        UserMsg.User.Builder userInfo = UserMsg.User.newBuilder();
        userInfo.setId(1);
        userInfo.setName("mushuwei");
        userInfo.setName("24");
        UserMsg.User user = userInfo.build();
        // 將資料寫到輸出流
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        user.writeTo(output);
        // 將資料序列化後傳送
        byte[] byteArray = output.toByteArray();
        // 接收到流並讀取
        ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
        // 反序列化
        UserMsg.User userInfo2 = UserMsg.User.parseFrom(input);
        log.info("id:" + userInfo2.getId());
        log.info("name:" + userInfo2.getName());
        log.info("age:" + userInfo2.getAge());

    }
}
複製程式碼

注:這裡說明一點,因為protobuf是通過二進位制進行傳輸,所以需要注意下相應的編碼。還有使用protobuf也需要注意一下一次傳輸的最大位元組長度。

輸出結果:

17:28:07.914 [main] INFO com.sanshengshui.nettyspringbootprotostuff.NettySpringbootProtostuffApplicationTests - id:1
17:28:07.919 [main] INFO com.sanshengshui.nettyspringbootprotostuff.NettySpringbootProtostuffApplicationTests - name:24
17:28:07.919 [main] INFO com.sanshengshui.nettyspringbootprotostuff.NettySpringbootProtostuffApplicationTests - age:0
複製程式碼

Netty整合SpringBoot並使用Protobuf進行資料傳輸

Netty整合springboot並使用protobuf進行資料傳輸

說明:如果想直接獲取工程那麼可以直接跳到底部,通過連結下載工程程式碼。

開發準備

環境要求

JDK::1.8 Netty::4.0或以上(不包括5) Protobuf:3.0或以上

如果對Netty不熟的話,可以看看我之前寫的netty 之 telnet HelloWorld 詳解。大神請無視~。~ 地址:www.cnblogs.com/sanshengshu…

首先還是Maven的相關依賴:

 <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <netty-all.version>4.1.29.Final</netty-all.version>
        <protobuf.version>3.6.1</protobuf.version>
        <grpc.version>1.15.0</grpc.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--netty jar包匯入-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty-all.version}</version>
        </dependency>

        <!--使用grpc優雅的編譯protobuf-->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobuf.version}</version>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>

        <!--lombok用於日誌,實體類的重複程式碼書寫-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
複製程式碼

新增了相應的maven依賴之後!我們還需要新增grpc優雅的編譯protobuf的外掛:

 <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.5.0.Final</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.7</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.2.1</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.0.2</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.0.0</version>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-protoc</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>com.google.protobuf</groupId>
                                    <artifactId>protoc</artifactId>
                                    <version>${protobuf.version}</version>
                                    <classifier>${os.detected.classifier}</classifier>
                                    <type>exe</type>
                                    <overWrite>true</overWrite>
                                    <outputDirectory>${project.build.directory}</outputDirectory>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.0</version>
                <configuration>
                    <!--
                      The version of protoc must match protobuf-java. If you don't depend on
                      protobuf-java directly, you will be transitively depending on the
                      protobuf-java version that grpc depends on.
                    -->
                    <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
                    </protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.0.0:exe:${os.detected.classifier}
                    </pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
複製程式碼

此外我們還需要對application.yml配置檔案作一點修改:

server:
  enabled: true
  bind_address: 0.0.0.0
  bind_port: 9876
  netty:
    #不進行記憶體洩露的檢測
    leak_detector_level: DISABLED
    boss_group_thread_count: 1
    worker_group_thread_count: 12
    #最大負載大小
    max_payload_size: 65536
複製程式碼

專案結構

  netty-springboot-protobuf
    ├── client
      ├── NettyClient.class -- 客戶端啟動類
      ├── NettyClientHandler.class -- 客戶端邏輯處理類
      ├── NettyClientHandler.class -- 客戶端初始化類
    ├── server 
      ├── NettyServer.class -- 服務端啟動類
      ├── NettyServerHandler -- 服務端邏輯處理類
      ├── NettyServerInitializer -- 服務端初始化類
    ├── proto
      ├── user.proto -- protobuf檔案
複製程式碼

程式碼編寫

程式碼模組主要分為服務端和客戶端。 主要實現的業務邏輯: 服務端啟動成功之後,客戶端也啟動成功,這時服務端會傳送一條protobuf格式的資訊給客戶端,然後客戶端給予相應的應答。客戶端與服務端連線成功之後,客戶端每個一段時間會傳送心跳指令給服務端,告訴服務端該客戶端還存過中,如果客戶端沒有在指定的時間傳送資訊,服務端會關閉與該客戶端的連線。當客戶端無法連線到服務端之後,會每隔一段時間去嘗試重連,只到重連成功!

服務端

首先是編寫服務端的啟動類,相應的註釋在程式碼中寫得很詳細了,這裡也不再過多講述了。不過需要注意的是,在之前的我寫的Netty文章中,是通過main方法直接啟動服務端,因此是直接new一個物件的。而在和SpringBoot整合之後,我們需要將Netty交給springBoot去管理,所以這裡就用了相應的註解。 程式碼如下:

@Service("nettyServer")
@Slf4j
public class NettyServer {
    /**
     * 通過springboot讀取靜態資源,實現netty配置檔案的讀寫
     */

    @Value("${server.bind_port}")
    private Integer port;

    @Value("${server.netty.boss_group_thread_count}")
    private Integer bossGroupThreadCount;

    @Value("${server.netty.worker_group_thread_count}")
    private Integer workerGroupThreadCount;

    @Value("${server.netty.leak_detector_level}")
    private String leakDetectorLevel;

    @Value("${server.netty.max_payload_size}")
    private Integer maxPayloadSize;

    private  ChannelFuture channelFuture;
    private  EventLoopGroup bossGroup;
    private  EventLoopGroup workerGroup;


    @PostConstruct
    public void init() throws Exception {
            log.info("Setting resource leak detector level to {}",leakDetectorLevel);
            ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.valueOf(leakDetectorLevel.toUpperCase()));

            log.info("Starting Server");
            //建立boss執行緒組 用於服務端接受客戶端的連線
            bossGroup = new NioEventLoopGroup(bossGroupThreadCount);
            // 建立 worker 執行緒組 用於進行 SocketChannel 的資料讀寫
            workerGroup = new NioEventLoopGroup(workerGroupThreadCount);
            // 建立 ServerBootstrap 物件
            ServerBootstrap b = new ServerBootstrap();
            //設定使用的EventLoopGroup
            b.group(bossGroup, workerGroup)
                    //設定要被例項化的為 NioServerSocketChannel 類
                    .channel(NioServerSocketChannel.class)
                    // 設定 NioServerSocketChannel 的處理器
                    .handler(new LoggingHandler(LogLevel.INFO))
                    // 設定連入服務端的 Client 的 SocketChannel 的處理器
                    .childHandler(new NettyServerInitializer());
            // 繫結埠,並同步等待成功,即啟動服務端
            channelFuture = b.bind(port).sync();

            log.info("Server started!");

    }

    @PreDestroy
    public void shutdown() throws InterruptedException {
        log.info("Stopping Server");
        try {
            // 監聽服務端關閉,並阻塞等待
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 優雅關閉兩個 EventLoopGroup 物件
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
        log.info("server stopped!");

    }

}

複製程式碼

服務端主類編寫完畢之後,我們再來設定下相應的過濾條件。 這裡需要繼承Netty中ChannelInitializer類,然後重寫initChannel該方法,進行新增相應的設定,如心跳超時設定,傳輸協議設定,以及相應的業務實現類。 程式碼如下:

  public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {


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

        //入參說明: 讀超時時間、寫超時時間、所有型別的超時時間、時間格式
        ph.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
        // 解碼和編碼,應和客戶端一致
        //傳輸的協議 Protobuf
        ph.addLast(new ProtobufVarint32FrameDecoder());
        ph.addLast(new ProtobufDecoder(UserMsg.User.getDefaultInstance()));
        ph.addLast(new ProtobufVarint32LengthFieldPrepender());
        ph.addLast(new ProtobufEncoder());

        //業務邏輯實現類
        ph.addLast("nettyServerHandler", new NettyServerHandler());
    }
}

複製程式碼

服務相關的設定的程式碼寫完之後,我們再來編寫主要的業務程式碼。 使用Netty編寫業務層的程式碼,我們需要繼承ChannelInboundHandlerAdapterSimpleChannelInboundHandler類,在這裡順便說下它們兩的區別吧。 繼承SimpleChannelInboundHandler類之後,會在接收到資料後會自動release掉資料佔用的Bytebuffer資源。並且繼承該類需要指定資料格式。 而繼承ChannelInboundHandlerAdapter則不會自動釋放,需要手動呼叫ReferenceCountUtil.release()等方法進行釋放。繼承該類不需要指定資料格式。 所以在這裡,個人推薦服務端繼承ChannelInboundHandlerAdapter,手動進行釋放,防止資料未處理完就自動釋放了。而且服務端可能有多個客戶端進行連線,並且每一個客戶端請求的資料格式都不一致,這時便可以進行相應的處理。 客戶端根據情況可以繼承SimpleChannelInboundHandler類。好處是直接指定好傳輸的資料格式,就不需要再進行格式的轉換了。

程式碼如下:

@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    /** 空閒次數 */
    private AtomicInteger idle_count = new AtomicInteger(1);
    /** 傳送次數 */
    private AtomicInteger count = new AtomicInteger(1);


    /**
     * 建立連線時,傳送一條訊息
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("連線的客戶端地址:" + ctx.channel().remoteAddress());
        UserMsg.User user = UserMsg.User.newBuilder().setId(1).setAge(24).setName("穆書偉").setState(0).build();
        ctx.writeAndFlush(user);
        super.channelActive(ctx);
    }

    /**
     * 超時處理 如果5秒沒有接受客戶端的心跳,就觸發; 如果超過兩次,則直接關閉;
     */
    @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())) {
                log.info("已經5秒沒有接收到客戶端的資訊了");
                if (idle_count.get() > 1) {
                    log.info("關閉這個不活躍的channel");
                    ctx.channel().close();
                }
                idle_count.getAndIncrement();
            }
        } else {
            super.userEventTriggered(ctx, obj);
        }
    }

    /**
     * 業務邏輯處理
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("第" + count.get() + "次" + ",服務端接受的訊息:" + msg);
        try {
            // 如果是protobuf型別的資料
            if (msg instanceof UserMsg.User) {
                UserMsg.User user = (UserMsg.User) msg;
                if (user.getState() == 1) {
                    log.info("客戶端業務處理成功!");
                } else if(user.getState() == 2){
                    log.info("接受到客戶端傳送的心跳!");
                }else{
                    log.info("未知命令!");
                }
            } else {
                log.info("未知資料!" + msg);
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ReferenceCountUtil.release(msg);
        }
        count.getAndIncrement();
    }

    /**
     * 異常處理
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
複製程式碼

還有個服務端的啟動類,之前是通過main方法直接啟動, 不過這裡改成了通過springBoot進行啟動,差別不大。 程式碼如下:

@SpringBootApplication
@ComponentScan({"com.sanshengshui.netty.server"})
public class NettyServerApp {
    /**
     * @param args
     */
    public static void main(String[] args) {
       SpringApplication.run(NettyServerApp.class);
    }
}

複製程式碼

到這裡服務端相應的程式碼就編寫完畢了:bulb:。

客戶端

客戶端這邊的程式碼和服務端的很多地方都類似,我就不再過多細說了,主要將一些不同的程式碼拿出來簡單的講述下。 首先是客戶端的主類,基本和服務端的差不多,也就是多了監聽的埠和一個監聽器(用來監聽是否和服務端斷開連線,用於重連)。 主要實現的程式碼邏輯如下:

     /**
     * 重連
     */
    public void doConnect(Bootstrap bootstrap, EventLoopGroup eventLoopGroup) {
        try {
            if (bootstrap != null) {
                bootstrap.group(eventLoopGroup);
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
                bootstrap.handler(new NettyClientInitializer());
                bootstrap.remoteAddress(host, port);
                f = bootstrap.connect().addListener((ChannelFuture futureListener) -> {
                    final EventLoop eventLoop = futureListener.channel().eventLoop();
                    if (!futureListener.isSuccess()) {
                        log.info("與服務端斷開連線!在10s之後準備嘗試重連!");
                        eventLoop.schedule(() -> doConnect(new Bootstrap(), eventLoop), 10, TimeUnit.SECONDS);
                    }
                });
                if(initFalg){
                    log.info("Netty客戶端啟動成功!");
                    initFalg=false;
                }
            }
        } catch (Exception e) {
            log.info("客戶端連線失敗!"+e.getMessage());
        }

    }
複製程式碼

注:監聽器這塊的實現用的是JDK1.8的寫法。

客戶端過濾其這塊基本和服務端一直。不過需要注意的是,傳輸協議、編碼和解碼應該一致,還有心跳的讀寫時間應該小於服務端所設定的時間。 改動的程式碼如下:

    ChannelPipeline ph = ch.pipeline();
        /*
         * 解碼和編碼,應和服務端一致
         * */
        //入參說明: 讀超時時間、寫超時時間、所有型別的超時時間、時間格式
        ph.addLast(new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS));
複製程式碼

客戶端的業務程式碼邏輯。 主要實現的幾點邏輯是心跳按時傳送以及解析服務傳送的protobuf格式的資料。 這裡比服務端多個個註解, 該註解Sharable主要是為了多個handler可以被多個channel安全地共享,也就是保證執行緒安全。 廢話就不多說了,程式碼如下:

    @ChannelHandler.Sharable
@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    @Autowired
    private NettyClient nettyClient;

    /** 迴圈次數 */
    private AtomicInteger fcount = new AtomicInteger(1);

    /**
     * 建立連線時
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("建立連線時:" + new Date());
        ctx.fireChannelActive();
    }

    /**
     * 關閉連線時
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("關閉連線時:" + new Date());
        final EventLoop eventLoop = ctx.channel().eventLoop();
        nettyClient.doConnect(new Bootstrap(), eventLoop);
        super.channelInactive(ctx);
    }

    /**
     * 心跳請求處理 每4秒傳送一次心跳請求;
     *
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
        log.info("迴圈請求的時間:" + new Date() + ",次數" + fcount.get());
        if (obj instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) obj;
            // 如果寫通道處於空閒狀態,就傳送心跳命令
            if (IdleState.WRITER_IDLE.equals(event.state())) {
                UserMsg.User.Builder userState = UserMsg.User.newBuilder().setState(2);
                ctx.channel().writeAndFlush(userState);
                fcount.getAndIncrement();
            }
        }
    }

    /**
     * 業務邏輯處理
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 如果不是protobuf型別的資料
        if (!(msg instanceof UserMsg.User)) {
            log.info("未知資料!" + msg);
            return;
        }
        try {

            // 得到protobuf的資料
            UserMsg.User userMsg = (UserMsg.User) msg;
            // 進行相應的業務處理。。。
            // 這裡就從簡了,只是列印而已
            log.info(
                    "客戶端接受到的使用者資訊。編號:" + userMsg.getId() + ",姓名:" + userMsg.getName() + ",年齡:" + userMsg.getAge());

            // 這裡返回一個已經接受到資料的狀態
            UserMsg.User.Builder userState = UserMsg.User.newBuilder().setState(1);
            ctx.writeAndFlush(userState);
            log.info("成功傳送給服務端!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

}
複製程式碼

那麼到這裡客戶端的程式碼也編寫完畢了:bulb:。

功能測試

protobuf傳輸

首先啟動服務端,然後再啟動客戶端。 我們來看看結果是否如上述所說。

服務端輸出結果:

2018-10-03 19:58:41.098  INFO 23644 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 第1次,服務端接受的訊息:state: 1

2018-10-03 19:58:41.098  INFO 23644 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 客戶端業務處理成功!
2018-10-03 19:58:45.058  INFO 23644 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 第2次,服務端接受的訊息:state: 2

2018-10-03 19:58:45.059  INFO 23644 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 接受到客戶端傳送的心跳!
2018-10-03 19:58:49.060  INFO 23644 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 第3次,服務端接受的訊息:state: 2

2018-10-03 19:58:49.061  INFO 23644 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 接受到客戶端傳送的心跳!
2018-10-03 19:58:53.063  INFO 23644 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 第4次,服務端接受的訊息:state: 2

2018-10-03 19:58:53.064  INFO 23644 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 接受到客戶端傳送的心跳!
2018-10-03 19:58:57.066  INFO 23644 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 第5次,服務端接受的訊息:state: 2
複製程式碼

客戶端輸入結果:

2018-10-03 19:58:40.733  INFO 23737 --- [           main] c.sanshengshui.netty.client.NettyClient  : Netty客戶端啟動成功!
2018-10-03 19:58:40.897  INFO 23737 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 建立連線時:Wed Oct 03 19:58:40 CST 2018
2018-10-03 19:58:41.033  INFO 23737 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 客戶端接受到的使用者資訊。編號:1,姓名:穆書偉,年齡:24
2018-10-03 19:58:41.044  INFO 23737 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 成功傳送給服務端!
2018-10-03 19:58:41.053  INFO 23737 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-10-03 19:58:41.067  INFO 23737 --- [           main] com.sanshengshui.netty.NettyClientApp    : Started NettyClientApp in 1.73 seconds (JVM running for 2.632)
2018-10-03 19:58:45.054  INFO 23737 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 迴圈請求的時間:Wed Oct 03 19:58:45 CST 2018,次數1
2018-10-03 19:58:49.057  INFO 23737 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 迴圈請求的時間:Wed Oct 03 19:58:49 CST 2018,次數2
2018-10-03 19:58:53.060  INFO 23737 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 迴圈請求的時間:Wed Oct 03 19:58:53 CST 2018,次數3
2018-10-03 19:58:57.063  INFO 23737 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 迴圈請求的時間:Wed Oct 03 19:58:57 CST 2018,次數4
2018-10-03 19:59:01.066  INFO 23737 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 迴圈請求的時間:Wed Oct 03 19:59:01 CST 2018,次數5

複製程式碼

通過列印資訊可以看出如上述所說。

斷線重連

接下來我們再來看看客戶端是否能夠實現重連。 先啟動客戶端,再啟動服務端。

客戶端輸入結果:

2018-10-03 20:02:33.549  INFO 23990 --- [ntLoopGroup-2-1] c.sanshengshui.netty.client.NettyClient  : 與服務端斷開連線!在10s之後準備嘗試重連!
2018-10-03 20:02:43.571  INFO 23990 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 建立連線時:Wed Oct 03 20:02:43 CST 2018
2018-10-03 20:02:43.718  INFO 23990 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 客戶端接受到的使用者資訊。編號:1,姓名:穆書偉,年齡:24
2018-10-03 20:02:43.727  INFO 23990 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 成功傳送給服務端!
2018-10-03 20:02:47.733  INFO 23990 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 迴圈請求的時間:Wed Oct 03 20:02:47 CST 2018,次數1
2018-10-03 20:02:51.735  INFO 23990 --- [ntLoopGroup-2-1] c.s.netty.client.NettyClientHandler      : 迴圈請求的時間:Wed Oct 03 20:02:51 CST 2018,次數2

複製程式碼

服務端輸出結果:

2018-10-03 20:02:43.661  INFO 24067 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 連線的客戶端地址:/127.0.0.1:55690
2018-10-03 20:02:43.760  INFO 24067 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 第1次,服務端接受的訊息:state: 1

2018-10-03 20:02:43.760  INFO 24067 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 客戶端業務處理成功!
2018-10-03 20:02:47.736  INFO 24067 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 第2次,服務端接受的訊息:state: 2

2018-10-03 20:02:47.737  INFO 24067 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 接受到客戶端傳送的心跳!
2018-10-03 20:02:51.736  INFO 24067 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 第3次,服務端接受的訊息:state: 2
複製程式碼

結果也如上述所說!

讀寫超時

服務端輸出結果:

2018-10-03 20:12:19.193  INFO 24507 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 連線的客戶端地址:/127.0.0.1:56132
2018-10-03 20:12:24.173  INFO 24507 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 已經5秒沒有接收到客戶端的資訊了
2018-10-03 20:12:29.171  INFO 24507 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 已經5秒沒有接收到客戶端的資訊了
2018-10-03 20:12:29.172  INFO 24507 --- [ntLoopGroup-3-1] c.s.netty.server.NettyServerHandler      : 關閉這個不活躍的channel
複製程式碼

telnet輸出結果:

如下圖:

Netty整合SpringBoot並使用Protobuf進行資料傳輸

其它

關於netty整合springboot並使用protobuf進行資料傳輸到這裡就結束了。

netty整合springboot並使用protobuf進行資料傳輸 專案工程地址: github.com/sanshengshu…

對了,也有Netty整合的其他中介軟體專案工程地址: github.com/sanshengshu…

原創不易,如果感覺不錯,希望給個推薦!您的支援是我寫作的最大動力!

版權宣告: 作者:穆書偉

部落格園出處:www.cnblogs.com/sanshengshu…

github出處:github.com/sanshengshu…    

個人部落格出處:sanshengshui.github.io/

相關文章