本篇文章主要介紹的是SpringBoot整合Netty以及使用Protobuf進行數據傳輸的相關內容。Protobuf會介紹下用法,至於Netty在netty 之 telnet HelloWorld 詳解中已經介紹過了,這裏就再也不過多細說了。html
Protocol Buffer是Google的語言中立的,平臺中立的,可擴展機制的,用於序列化結構化數據 - 對比XML,但更小,更快,更簡單。您能夠定義數據的結構化,而後可使用特殊生成的源代碼輕鬆地在各類數據流中使用各類語言編寫和讀取結構化數據。java
官網地址: developers.google.com/protocol-bu…git
這裏的使用就只介紹Java相關的使用。具體protobuf3的使用能夠看Protobuf 語言指南(proto3)。 首先咱們須要在src/main文件夾下創建一個proto文件夾,而後在該文件夾新建一個user.proto文件,此文件定義咱們須要傳輸的文件。github
注:使用grpc方式編譯**.proto**時,會默認掃描src/main/proto文件夾下的protobuf文件。spring
例如咱們須要定義一個用戶的信息,包含的字段主要有編號、名稱、年齡。 那麼該protobuf文件的格式以下: 注:這裏使用的是proto3,相關的註釋我已寫了,這裏便再也不過多講述了。須要注意一點的是proto文件和生成的Java文件名稱不能一致!apache
//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文件就能夠了。由於能自動掃描到此類。詳情請看下圖:bootstrap
注:生成protobuf的文件軟件和測試的protobuf文件我也整合到該項目中了,能夠直接獲取的。安全
Java文件生成好以後,咱們再來看怎麼使用。 這裏我就直接貼代碼了,而且將註釋寫在代碼中,應該更容易理解些吧。。。 代碼示例:springboot
@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也須要注意一下一次傳輸的最大字節長度。bash
輸出結果:
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
複製代碼
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編寫業務層的代碼,咱們須要繼承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);
}
}
複製代碼
到這裏服務端相應的代碼就編寫完畢了: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:。
首先啓動服務端,而後再啓動客戶端。 咱們來看看結果是否如上述所說。
服務端輸出結果:
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進行數據傳輸 項目工程地址: github.com/sanshengshu…
對了,也有Netty整合的其餘中間件項目工程地址: github.com/sanshengshu…
原創不易,若是感受不錯,但願給個推薦!您的支持是我寫做的最大動力!
版權聲明: 做者:穆書偉
博客園出處:www.cnblogs.com/sanshengshu…
github出處:github.com/sanshengshu…
我的博客出處:sanshengshui.github.io/