Springboot+Netty搭建TCP客户端-多客户端

川长思鸟来 2024-02-19 20:21 148阅读 0赞

之前搭建了一个Springboot+Netty服务端的应用,既然有服务端,自然也有客户端的应用,现在搭建一个Springboot+Netty客户端的应用Demo程序,多客户端方式,使用服务端和客户端进行联调测试,也可以用tcp的小工具来测试(中文可能乱码)

SpringBoot+Netty实现TCP服务端客户端的源码Demo

新建Springboot的maven项目,pom.xml文件导入依赖包

  1. <?xml version="1.0"?>
  2. <project
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  4. xmlns="http://maven.apache.org/POM/4.0.0"
  5. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  6. <modelVersion>4.0.0</modelVersion>
  7. <parent>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-parent</artifactId>
  10. <version>2.0.5.RELEASE</version>
  11. <relativePath />
  12. </parent>
  13. <groupId>boot.base.tcp.client</groupId>
  14. <artifactId>boot-example-base-tcp-client-2.0.5</artifactId>
  15. <version>0.0.1-SNAPSHOT</version>
  16. <name>boot-example-base-tcp-client-2.0.5</name>
  17. <url>http://maven.apache.org</url>
  18. <properties>
  19. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  20. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  21. <java.version>1.8</java.version>
  22. </properties>
  23. <dependencies>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-web</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>io.netty</groupId>
  30. <artifactId>netty-all</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>io.springfox</groupId>
  34. <artifactId>springfox-swagger2</artifactId>
  35. <version>2.9.2</version>
  36. </dependency>
  37. <dependency>
  38. <groupId>com.github.xiaoymin</groupId>
  39. <artifactId>swagger-bootstrap-ui</artifactId>
  40. <version>1.9.2</version>
  41. </dependency>
  42. </dependencies>
  43. <build>
  44. <plugins>
  45. <!-- 打包成一个可执行jar -->
  46. <plugin>
  47. <groupId>org.springframework.boot</groupId>
  48. <artifactId>spring-boot-maven-plugin</artifactId>
  49. <executions>
  50. <execution>
  51. <goals>
  52. <goal>repackage</goal>
  53. </goals>
  54. </execution>
  55. </executions>
  56. </plugin>
  57. </plugins>
  58. </build>
  59. </project>

Springboot启动类,启动一个Netty的客户端

  1. package boot.example.tcp.client;
  2. import boot.example.tcp.client.netty.BootNettyClientThread;
  3. import org.springframework.boot.CommandLineRunner;
  4. import org.springframework.boot.SpringApplication;
  5. import org.springframework.boot.autoconfigure.SpringBootApplication;
  6. import org.springframework.scheduling.annotation.Async;
  7. import org.springframework.scheduling.annotation.EnableAsync;
  8. import org.springframework.scheduling.annotation.EnableScheduling;
  9. /**
  10. * 蚂蚁舞
  11. */
  12. @SpringBootApplication
  13. @EnableAsync
  14. @EnableScheduling
  15. public class BootNettyClientApplication implements CommandLineRunner{
  16. public static void main( String[] args ) {
  17. SpringApplication app = new SpringApplication(BootNettyClientApplication.class);
  18. app.run(args);
  19. System.out.println( "Hello World!" );
  20. }
  21. @Async
  22. @Override
  23. public void run(String... args) throws Exception {
  24. /**
  25. * 使用异步注解方式启动netty客户端服务
  26. */
  27. int port = 6655;
  28. String address = "127.0.0.1";
  29. int count = 10; // 模拟多个客户端
  30. for(int i = 0; i < count; i++) {
  31. BootNettyClientThread thread = new BootNettyClientThread(port, address);
  32. thread.start();
  33. }
  34. }
  35. }

Netty的client类

  1. package boot.example.tcp.client.netty;
  2. import io.netty.bootstrap.Bootstrap;
  3. import io.netty.channel.Channel;
  4. import io.netty.channel.ChannelFuture;
  5. import io.netty.channel.ChannelOption;
  6. import io.netty.channel.EventLoopGroup;
  7. import io.netty.channel.nio.NioEventLoopGroup;
  8. import io.netty.channel.socket.SocketChannel;
  9. import io.netty.channel.socket.nio.NioSocketChannel;
  10. /**
  11. *
  12. * netty 客户端
  13. * 蚂蚁舞
  14. */
  15. public class BootNettyClient {
  16. public void connect(int port, String host) throws Exception{
  17. /**
  18. * 客户端的NIO线程组
  19. *
  20. */
  21. EventLoopGroup group = new NioEventLoopGroup();
  22. try {
  23. /**
  24. * Bootstrap 是一个启动NIO服务的辅助启动类 客户端的
  25. */
  26. Bootstrap bootstrap = new Bootstrap();
  27. bootstrap = bootstrap.group(group);
  28. bootstrap = bootstrap.channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true);
  29. /**
  30. * 设置 I/O处理类,主要用于网络I/O事件,记录日志,编码、解码消息
  31. */
  32. bootstrap = bootstrap.handler(new BootNettyChannelInitializer<SocketChannel>());
  33. /**
  34. * 连接服务端
  35. */
  36. ChannelFuture future = bootstrap.connect(host, port).sync();
  37. if(future.isSuccess()) {
  38. Channel channel = future.channel();
  39. String id = future.channel().id().toString();
  40. BootNettyClientChannel bootNettyClientChannel = new BootNettyClientChannel();
  41. bootNettyClientChannel.setChannel(channel);
  42. bootNettyClientChannel.setCode("clientId:"+id);
  43. BootNettyClientChannelCache.save("clientId:"+id, bootNettyClientChannel);
  44. System.out.println("netty client start success="+id);
  45. /**
  46. * 等待连接端口关闭
  47. */
  48. future.channel().closeFuture().sync();
  49. }
  50. } finally {
  51. /**
  52. * 退出,释放资源
  53. */
  54. group.shutdownGracefully().sync();
  55. }
  56. }
  57. }

通道初始化

  1. package boot.example.tcp.client.netty;
  2. import io.netty.channel.Channel;
  3. import io.netty.channel.ChannelHandler;
  4. import io.netty.channel.ChannelInitializer;
  5. import io.netty.handler.codec.string.StringDecoder;
  6. import io.netty.handler.codec.string.StringEncoder;
  7. import io.netty.util.CharsetUtil;
  8. /**
  9. * 通道初始化
  10. * 蚂蚁舞
  11. */
  12. @ChannelHandler.Sharable
  13. public class BootNettyChannelInitializer<SocketChannel> extends ChannelInitializer<Channel> {
  14. @Override
  15. protected void initChannel(Channel ch) throws Exception {
  16. ch.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
  17. ch.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
  18. /**
  19. * 自定义ChannelInboundHandlerAdapter
  20. */
  21. ch.pipeline().addLast(new BootNettyChannelInboundHandlerAdapter());
  22. }
  23. }

客户端I/O数据读写处理类

  1. package boot.example.tcp.client.netty;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  4. import io.netty.channel.ChannelHandler;
  5. import io.netty.channel.ChannelHandlerContext;
  6. import io.netty.channel.ChannelInboundHandlerAdapter;
  7. /**
  8. *
  9. * I/O数据读写处理类
  10. * 蚂蚁舞
  11. */
  12. @ChannelHandler.Sharable
  13. public class BootNettyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter{
  14. /**
  15. * 从服务端收到新的数据时,这个方法会在收到消息时被调用
  16. */
  17. @Override
  18. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception, IOException {
  19. if(msg == null){
  20. return;
  21. }
  22. System.out.println("channelRead:read msg:"+msg.toString());
  23. BootNettyClientChannel bootNettyClientChannel = BootNettyClientChannelCache.get("clientId:"+ctx.channel().id().toString());
  24. if(bootNettyClientChannel != null){
  25. System.out.println("to do");
  26. bootNettyClientChannel.setLast_data(msg.toString());
  27. }
  28. //回应服务端
  29. //ctx.write("I got server message thanks server!");
  30. }
  31. /**
  32. * 从服务端收到新的数据、读取完成时调用
  33. */
  34. @Override
  35. public void channelReadComplete(ChannelHandlerContext ctx) throws IOException {
  36. System.out.println("channelReadComplete");
  37. ctx.flush();
  38. }
  39. /**
  40. * 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时
  41. */
  42. @Override
  43. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException {
  44. System.out.println("exceptionCaught");
  45. cause.printStackTrace();
  46. ctx.close();//抛出异常,断开与客户端的连接
  47. }
  48. /**
  49. * 客户端与服务端第一次建立连接时 执行
  50. */
  51. @Override
  52. public void channelActive(ChannelHandlerContext ctx) throws Exception, IOException {
  53. super.channelActive(ctx);
  54. InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
  55. String clientIp = inSocket.getAddress().getHostAddress();
  56. System.out.println(clientIp);
  57. }
  58. /**
  59. * 客户端与服务端 断连时 执行
  60. */
  61. @Override
  62. public void channelInactive(ChannelHandlerContext ctx) throws Exception, IOException {
  63. super.channelInactive(ctx);
  64. InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
  65. String clientIp = inSocket.getAddress().getHostAddress();
  66. ctx.close(); //断开连接时,必须关闭,否则造成资源浪费
  67. System.out.println("channelInactive:"+clientIp);
  68. }
  69. }

建立channel保存多客户端BootNettyClientChannel

  1. package boot.example.tcp.client.netty;
  2. import io.netty.channel.Channel;
  3. /**
  4. * 蚂蚁舞
  5. */
  6. public class BootNettyClientChannel {
  7. // 连接客户端唯一的code
  8. private String code;
  9. // 客户端最新发送的消息内容
  10. private String last_data;
  11. private transient volatile Channel channel;
  12. public String getCode() {
  13. return code;
  14. }
  15. public void setCode(String code) {
  16. this.code = code;
  17. }
  18. public Channel getChannel() {
  19. return channel;
  20. }
  21. public void setChannel(Channel channel) {
  22. this.channel = channel;
  23. }
  24. public String getLast_data() {
  25. return last_data;
  26. }
  27. public void setLast_data(String last_data) {
  28. this.last_data = last_data;
  29. }
  30. }
  31. BootNettyClientChannelCache
  32. package boot.example.tcp.client.netty;
  33. import java.util.Map;
  34. import java.util.concurrent.ConcurrentHashMap;
  35. /**
  36. * 蚂蚁舞
  37. */
  38. public class BootNettyClientChannelCache {
  39. public static volatile Map<String, BootNettyClientChannel> channelMapCache = new ConcurrentHashMap<String, BootNettyClientChannel>();
  40. public static void add(String code, BootNettyClientChannel channel){
  41. channelMapCache.put(code,channel);
  42. }
  43. public static BootNettyClientChannel get(String code){
  44. return channelMapCache.get(code);
  45. }
  46. public static void remove(String code){
  47. channelMapCache.remove(code);
  48. }
  49. public static void save(String code, BootNettyClientChannel channel) {
  50. if(channelMapCache.get(code) == null) {
  51. add(code,channel);
  52. }
  53. }
  54. }

netty的启动BootNettyClientThread

  1. package boot.example.tcp.client.netty;
  2. /**
  3. *
  4. * netty 客户端
  5. * 蚂蚁舞
  6. */
  7. public class BootNettyClientThread extends Thread {
  8. private final int port;
  9. private final String address;
  10. public BootNettyClientThread(int port, String address){
  11. this.port = port;
  12. this.address = address;
  13. }
  14. public void run() {
  15. try {
  16. new BootNettyClient().connect(port, address);
  17. } catch (Exception e) {
  18. throw new RuntimeException(e);
  19. }
  20. }
  21. }

心跳使用定时器BootNettyHeartTimer

  1. package boot.example.tcp.client.netty;
  2. import io.netty.buffer.Unpooled;
  3. import org.springframework.scheduling.annotation.Scheduled;
  4. import org.springframework.stereotype.Service;
  5. import java.util.Map;
  6. /**
  7. * 蚂蚁舞
  8. */
  9. @Service
  10. public class BootNettyHeartTimer {
  11. // 使用定时器发送心跳
  12. @Scheduled(cron = "0/30 * * * * ?")
  13. public void heart_timer() {
  14. String back = "heart";
  15. if(BootNettyClientChannelCache.channelMapCache.size() > 0){
  16. for (Map.Entry<String, BootNettyClientChannel> entry : BootNettyClientChannelCache.channelMapCache.entrySet()) {
  17. BootNettyClientChannel bootNettyChannel = entry.getValue();
  18. if(bootNettyChannel != null && bootNettyChannel.getChannel().isOpen()){
  19. bootNettyChannel.getChannel().writeAndFlush(Unpooled.buffer().writeBytes(back.getBytes()));
  20. }
  21. }
  22. }
  23. }
  24. }

测试接口BootNettyClientController

  1. package boot.example.tcp.client.controller;
  2. import boot.example.tcp.client.netty.BootNettyClientChannel;
  3. import boot.example.tcp.client.netty.BootNettyClientChannelCache;
  4. import io.netty.buffer.Unpooled;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PostMapping;
  7. import org.springframework.web.bind.annotation.RequestParam;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import java.util.ArrayList;
  10. import java.util.HashMap;
  11. import java.util.List;
  12. import java.util.Map;
  13. /**
  14. * 蚂蚁舞
  15. */
  16. @RestController
  17. public class BootNettyClientController {
  18. @GetMapping("/list")
  19. public List<Map<String,String>> list() {
  20. List<Map<String,String>> list = new ArrayList<>();
  21. for (Map.Entry<String, BootNettyClientChannel> entry : BootNettyClientChannelCache.channelMapCache.entrySet()) {
  22. Map<String, String> map = new HashMap<String, String>();
  23. map.put("code", entry.getKey());
  24. //map.put("code", entry.getValue().getCode());
  25. map.put("last_data", entry.getValue().getLast_data());
  26. list.add(map);
  27. }
  28. return list;
  29. }
  30. @PostMapping("/reportAllClientDataToServer")
  31. public String reportAllClientDataToServer(@RequestParam(name="content", required = true) String content) {
  32. for (Map.Entry<String, BootNettyClientChannel> entry : BootNettyClientChannelCache.channelMapCache.entrySet()) {
  33. BootNettyClientChannel bootNettyChannel = entry.getValue();
  34. if(bootNettyChannel != null && bootNettyChannel.getChannel().isOpen()){
  35. bootNettyChannel.getChannel().writeAndFlush(Unpooled.buffer().writeBytes(content.getBytes()));
  36. }
  37. }
  38. return "ok";
  39. }
  40. @PostMapping("/reportClientDataToServer")
  41. public String downDataToClient(@RequestParam(name="code", required = true) String code, @RequestParam(name="content", required = true) String content) {
  42. BootNettyClientChannel bootNettyChannel = BootNettyClientChannelCache.get(code);
  43. if(bootNettyChannel != null && bootNettyChannel.getChannel().isOpen()){
  44. bootNettyChannel.getChannel().writeAndFlush(Unpooled.buffer().writeBytes(content.getBytes()));
  45. return "success";
  46. }
  47. return "fail";
  48. }
  49. }

SwaggerConfig测试方便

  1. package boot.example.tcp.client;
  2. import com.google.common.base.Predicates;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import springfox.documentation.builders.ApiInfoBuilder;
  6. import springfox.documentation.builders.PathSelectors;
  7. import springfox.documentation.builders.RequestHandlerSelectors;
  8. import springfox.documentation.service.ApiInfo;
  9. import springfox.documentation.spi.DocumentationType;
  10. import springfox.documentation.spring.web.plugins.Docket;
  11. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  12. /**
  13. * 蚂蚁舞
  14. */
  15. @Configuration
  16. @EnableSwagger2
  17. public class SwaggerConfig {
  18. @Bean
  19. public Docket createRestApi(){
  20. return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
  21. .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any())
  22. .paths(Predicates.not(PathSelectors.regex("/error.*")))
  23. .paths(PathSelectors.regex("/.*"))
  24. .build().apiInfo(apiInfo());
  25. }
  26. private ApiInfo apiInfo(){
  27. return new ApiInfoBuilder()
  28. .title("netty tcp 客户端demo")
  29. .description("netty tcp 客户端接口测试demo")
  30. .version("0.01")
  31. .build();
  32. }
  33. /**
  34. * http://localhost:8094/doc.html 地址和端口根据实际项目查看
  35. */
  36. }

客户端demo代码的目录结构

  1. ├─boot-example-base-tcp-client-2.0.5
  2. pom.xml
  3. ├─src
  4. ├─main
  5. ├─java
  6. └─boot
  7. └─example
  8. └─tcp
  9. └─client
  10. BootNettyClientApplication.java
  11. SwaggerConfig.java
  12. ├─controller
  13. BootNettyClientController.java
  14. └─netty
  15. BootNettyChannelInboundHandlerAdapter.java
  16. BootNettyChannelInitializer.java
  17. BootNettyClient.java
  18. BootNettyClientChannel.java
  19. BootNettyClientChannelCache.java
  20. BootNettyClientThread.java
  21. BootNettyHeartTimer.java
  22. └─resources
  23. application.properties
  24. └─test
  25. └─java
  26. └─boot
  27. └─example
  28. └─tcp
  29. └─client
  30. BootNettyClientApplicationTest.java

基本demo客户端代码就完成了,要进行测试了。

我这里不使用tcp服务端工具测试,之前使用之前使用netty搭建的服务端进行交互测试

地址Springboot+Netty搭建TCP服务端_蚂蚁舞的博客-CSDN博客

测试步骤

先启动springBoot+Netty的服务端代码

  1. 2023-01-20 10:25:55.677 INFO 2664 --- [ main] s.d.s.w.s.ApiListingReferenceScanner : Scanning for api listing references
  2. 2023-01-20 10:25:55.904 INFO 2664 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 6654 (http) with context path ''
  3. 2023-01-20 10:25:55.909 INFO 2664 --- [ main] b.e.t.server.BootNettyServerApplication : Started BootNettyServerApplication in 6.752 seconds (JVM running for 7.598)
  4. 2023-01-20 10:25:55.915 INFO 2664 --- [ main] .s.a.AnnotationAsyncExecutionInterceptor : No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either
  5. Hello World!
  6. netty server start success!

可以浏览器web访问

  1. http://localhost:6654/doc.html

再启动springBoot+Netty的客户端代码(多客户端啊,这里启动10个)

  1. int port = 6655;
  2. String address = "127.0.0.1";
  3. int count = 10; // 模拟多个客户端
  4. for(int i = 0; i < count; i++) {
  5. BootNettyClientThread thread = new BootNettyClientThread(port, address);
  6. thread.start();
  7. }
  8. 2023-01-20 10:29:01.955 INFO 13120 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
  9. 2023-01-20 10:29:01.966 INFO 13120 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 2147483647
  10. 2023-01-20 10:29:01.967 INFO 13120 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
  11. 2023-01-20 10:29:02.007 INFO 13120 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
  12. 2023-01-20 10:29:02.053 INFO 13120 --- [ main] s.d.s.w.s.ApiListingReferenceScanner : Scanning for api listing references
  13. 2023-01-20 10:29:02.205 INFO 13120 --- [ main] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
  14. 2023-01-20 10:29:02.257 INFO 13120 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8094 (http) with context path ''
  15. 2023-01-20 10:29:02.264 INFO 13120 --- [ main] b.e.t.client.BootNettyClientApplication : Started BootNettyClientApplication in 6.258 seconds (JVM running for 7.18)
  16. 2023-01-20 10:29:02.270 INFO 13120 --- [ main] .s.a.AnnotationAsyncExecutionInterceptor : No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either
  17. Hello World!
  18. 127.0.0.1
  19. 127.0.0.1
  20. 127.0.0.1
  21. 127.0.0.1
  22. 127.0.0.1
  23. 127.0.0.1
  24. 127.0.0.1
  25. 127.0.0.1
  26. 127.0.0.1
  27. 127.0.0.1
  28. netty client start success=8f7ca2ce
  29. netty client start success=987e23c5
  30. netty client start success=dc272839
  31. netty client start success=c74a53a9
  32. netty client start success=cbf85db3
  33. netty client start success=e6ff0519
  34. netty client start success=bc0fc00f
  35. netty client start success=29db84c8
  36. netty client start success=6e20d3e6
  37. netty client start success=30378f02

可以浏览客户端的web访问

507be38575e34f7394e5e7a232faf77a.png

可以看到客户端启动了是个客户端,服务端也给客户端返回了服务端创建成功的code(实际是netty的通道id,唯一的拿来使用的)

5ee4ebe9e1d348629d384408dee6a7be.png

可以看到服务端收到了来自客户端的心跳

我选取一个客户端来测试客户端给服务端发送消息(含中文)

  1. {
  2. "code": "clientId:bc0fc00f",
  3. "last_data": "server:bd255b2d"
  4. }

394244a5dc244c98b9e49ee58a27af2e.png

可以看到给服务端发送的数据是

  1. 蚂蚁舞mywhtw147258#$%^

服务端接收到的最新数据

586e8d2999474f15b21c3da5eadd1358.png

  1. {
  2. "code": "server:bd255b2d",
  3. "report_last_data": "蚂蚁舞mywhtw147258#$%^"
  4. }

服务端控制台的打印日志

  1. channelId=bd255b2ddata=蚂蚁舞mywhtw147258#$%^
  2. channelReadComplete
  3. channelId=bd255b2ddata=蚂蚁舞mywhtw147258#$%^
  4. channelReadComplete

服务端给客户端发送消息的方式是一样的,以及服务端批量给客户端发送消息,还有多个客户端给服务端发送同样的消息,都是可以达到的。

基于springboot+netty的客户端和服务端就调通了,支持中文不乱码。

发表评论

表情:
评论列表 (有 0 条评论,148人围观)

还没有评论,来说两句吧...

相关阅读

    相关 cas客户

    今天大哥,就给了一个cas认证的包,和 一个cas证书 > 和一个 大片 web。xml 配置的 文档, > 让我部署到服务器上面 添加 cas 认证的包 ...

    相关 Es客户

    ES提供多种不同的客户端:   1、TransportClient ES提供的传统客户端,官方计划8.0版本删除此客户端。  2、RestClient  RestCl