前言
本篇文章主要介紹的是SpringBoot整合Netty以及使用Protobuf進行數據傳輸的相關內容。Protobuf會介紹下用法,至於Netty在netty 之 telnet HelloWorld 詳解中已經介紹過了,這里就不再過多細說了。
Protobuf
介紹
Protocol Buffer是Google的語言中立的,平台中立的,可擴展機制的,用於序列化結構化數據 - 對比XML,但更小,更快,更簡單。您可以定義數據的結構化,然后可以使用特殊生成的源代碼輕松地在各種數據流中使用各種語言編寫和讀取結構化數據。
官網地址: https://developers.google.com/protocol-buffers/
使用
這里的使用就只介紹Java相關的使用。具體protobuf3的使用可以看Protobuf 語言指南(proto3)。 首先我們需要在src/main文件夾下建立一個proto文件夾,然后在該文件夾新建一個user.proto文件,此文件定義我們需要傳輸的文件。
注:使用grpc方式編譯.proto時,會默認掃描src/main/proto文件夾下的protobuf文件。
例如我們需要定義一個用戶的信息,包含的字段主要有編號、名稱、年齡。 那么該protobuf文件的格式如下: 注:這里使用的是proto3,相關的注釋我已寫了,這里便不再過多講述了。需要注意一點的是proto文件和生成的Java文件名稱不能一致!
1 //proto3語法注解:如果您不這樣做,protobuf編譯器將假定您正在使用proto2,這必須是文件的第一個非空的非注釋行。
2 syntax = "proto3";
3 //生成的包名
4 option java_package = "com.sanshengshui.netty.protobuf"; 5 //生成的java名 6 option java_outer_classname = "UserMsg"; 7 8 message User{ 9 //ID 10 int32 id = 1; 11 //姓名 12 string name = 2; 13 //年齡 14 int32 age = 3; 15 //狀態 16 int32 state = 4; 17 }
創建好該文件之后,我們cd到該工程的根目錄下,執行mvn clean compile,輸入完之后,回車即可在target文件夾中看到已經生成好的Java文件,然后直接在工程中使用此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進行數據傳輸
說明:如果想直接獲取工程那么可以直接跳到底部,通過鏈接下載工程代碼。
開發准備
環境要求
JDK::1.8
Netty::4.0或以上(不包括5)
Protobuf:3.0或以上
如果對Netty不熟的話,可以看看我之前寫的netty 之 telnet HelloWorld 詳解。大神請無視~。~ 地址:https://www.cnblogs.com/sanshengshui/p/9726306.html
首先還是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去管理,所以這里就用了相應的注解。 代碼如下:
1 @Service("nettyServer")
2 @Slf4j 3 public class NettyServer { 4 /** 5 * 通過springboot讀取靜態資源,實現netty配置文件的讀寫 6 */ 7 8 @Value("${server.bind_port}") 9 private Integer port; 10 11 @Value("${server.netty.boss_group_thread_count}") 12 private Integer bossGroupThreadCount; 13 14 @Value("${server.netty.worker_group_thread_count}") 15 private Integer workerGroupThreadCount; 16 17 @Value("${server.netty.leak_detector_level}") 18 private String leakDetectorLevel; 19 20 @Value("${server.netty.max_payload_size}") 21 private Integer maxPayloadSize; 22 23 private ChannelFuture channelFuture; 24 private EventLoopGroup bossGroup; 25 private EventLoopGroup workerGroup; 26 27 28 @PostConstruct 29 public void init() throws Exception { 30 log.info("Setting resource leak detector level to {}",leakDetectorLevel); 31 ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.valueOf(leakDetectorLevel.toUpperCase())); 32 33 log.info("Starting Server"); 34 //創建boss線程組 用於服務端接受客戶端的連接 35 bossGroup = new NioEventLoopGroup(bossGroupThreadCount); 36 // 創建 worker 線程組 用於進行 SocketChannel 的數據讀寫 37 workerGroup = new NioEventLoopGroup(workerGroupThreadCount); 38 // 創建 ServerBootstrap 對象 39 ServerBootstrap b = new ServerBootstrap(); 40 //設置使用的EventLoopGroup 41 b.group(bossGroup, workerGroup) 42 //設置要被實例化的為 NioServerSocketChannel 類 43 .channel(NioServerSocketChannel.class) 44 // 設置 NioServerSocketChannel 的處理器 45 .handler(new LoggingHandler(LogLevel.INFO)) 46 // 設置連入服務端的 Client 的 SocketChannel 的處理器 47 .childHandler(new NettyServerInitializer()); 48 // 綁定端口,並同步等待成功,即啟動服務端 49 channelFuture = b.bind(port).sync(); 50 51 log.info("Server started!"); 52 53 } 54 55 @PreDestroy 56 public void shutdown() throws InterruptedException { 57 log.info("Stopping Server"); 58 try { 59 // 監聽服務端關閉,並阻塞等待 60 channelFuture.channel().closeFuture().sync(); 61 } finally { 62 // 優雅關閉兩個 EventLoopGroup 對象 63 workerGroup.shutdownGracefully(); 64 bossGroup.shutdownGracefully(); 65 } 66 log.info("server stopped!"); 67 68 } 69 70 }
服務端主類編寫完畢之后,我們再來設置下相應的過濾條件。 這里需要繼承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編寫業務層的代碼,我們需要繼承ChannelInboundHandlerAdapter 或SimpleChannelInboundHandler類,在這里順便說下它們兩的區別吧。 繼承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); } }
到這里服務端相應的代碼就編寫完畢了💡。
客戶端
客戶端這邊的代碼和服務端的很多地方都類似,我就不再過多細說了,主要將一些不同的代碼拿出來簡單的講述下。 首先是客戶端的主類,基本和服務端的差不多,也就是多了監聽的端口和一個監聽器(用來監聽是否和服務端斷開連接,用於重連)。 主要實現的代碼邏輯如下:
/**
* 重連
*/
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的寫法。
客戶端過濾其這塊基本和服務端一直。不過需要注意的是,傳輸協議、編碼和解碼應該一致,還有心跳的讀寫時間應該小於服務端所設置的時間。 改動的代碼如下:
1 ChannelPipeline ph = ch.pipeline();
2 /*
3 * 解碼和編碼,應和服務端一致
4 * */
5 //入參說明: 讀超時時間、寫超時時間、所有類型的超時時間、時間格式
6 ph.addLast(new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS));
客戶端的業務代碼邏輯。 主要實現的幾點邏輯是心跳按時發送以及解析服務發送的protobuf格式的數據。 這里比服務端多個個注解, 該注解Sharable主要是為了多個handler可以被多個channel安全地共享,也就是保證線程安全。 廢話就不多說了,代碼如下:
1 @ChannelHandler.Sharable
2 @Slf4j 3 public class NettyClientHandler extends ChannelInboundHandlerAdapter { 4 @Autowired 5 private NettyClient nettyClient; 6 7 /** 循環次數 */ 8 private AtomicInteger fcount = new AtomicInteger(1); 9 10 /** 11 * 建立連接時 12 */ 13 @Override 14 public void channelActive(ChannelHandlerContext ctx) throws Exception { 15 log.info("建立連接時:" + new Date()); 16 ctx.fireChannelActive(); 17 } 18 19 /** 20 * 關閉連接時 21 */ 22 @Override 23 public void channelInactive(ChannelHandlerContext ctx) throws Exception { 24 log.info("關閉連接時:" + new Date()); 25 final EventLoop eventLoop = ctx.channel().eventLoop(); 26 nettyClient.doConnect(new Bootstrap(), eventLoop); 27 super.channelInactive(ctx); 28 } 29 30 /** 31 * 心跳請求處理 每4秒發送一次心跳請求; 32 * 33 */ 34 @Override 35 public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception { 36 log.info("循環請求的時間:" + new Date() + ",次數" + fcount.get()); 37 if (obj instanceof IdleStateEvent) { 38 IdleStateEvent event = (IdleStateEvent) obj; 39 // 如果寫通道處於空閑狀態,就發送心跳命令 40 if (IdleState.WRITER_IDLE.equals(event.state())) { 41 UserMsg.User.Builder userState = UserMsg.User.newBuilder().setState(2); 42 ctx.channel().writeAndFlush(userState); 43 fcount.getAndIncrement(); 44 } 45 } 46 } 47 48 /** 49 * 業務邏輯處理 50 */ 51 @Override 52 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 53 // 如果不是protobuf類型的數據 54 if (!(msg instanceof UserMsg.User)) { 55 log.info("未知數據!" + msg); 56 return; 57 } 58 try { 59 60 // 得到protobuf的數據 61 UserMsg.User userMsg = (UserMsg.User) msg; 62 // 進行相應的業務處理。。。 63 // 這里就從簡了,只是打印而已 64 log.info( 65 "客戶端接受到的用戶信息。編號:" + userMsg.getId() + ",姓名:" + userMsg.getName() + ",年齡:" + userMsg.getAge()); 66 67 // 這里返回一個已經接受到數據的狀態 68 UserMsg.User.Builder userState = UserMsg.User.newBuilder().setState(1); 69 ctx.writeAndFlush(userState); 70 log.info("成功發送給服務端!"); 71 } catch (Exception e) { 72 e.printStackTrace(); 73 } finally { 74 ReferenceCountUtil.release(msg); 75 } 76 } 77 78 }
那么到這里客戶端的代碼也編寫完畢了💡。
功能測試
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進行數據傳輸 項目工程地址: https://github.com/sanshengshui/netty-learning-example/tree/master/netty-springboot-protobuf
對了,也有Netty整合的其他中間件項目工程地址: https://github.com/sanshengshui/netty-learning-example
原創不易,如果感覺不錯,希望給個推薦!您的支持是我寫作的最大動力!
版權聲明: 作者:穆書偉
博客園出處:https://www.cnblogs.com/sanshengshui
github出處:https://github.com/sanshengshui
個人博客出處:https://sanshengshui.github.io/