Netty интегрирует SpringBoot и использует Protobuf для передачи данных

Java задняя часть Netty protobuf

Netty

предисловие

В этой статье в основном представлена ​​интеграция SpringBoot с Netty и использование Protobuf для передачи данных. Protobuf введет использование, как для Netty вПодробное объяснение telnet HelloWorld от nettyОн уже был представлен в , поэтому я не буду вдаваться в подробности здесь.

Protobuf

вводить

Протокольные буферы — это независимый от языка и платформы расширяемый механизм Google для сериализации структурированных данных — по сравнению с XML, но меньше, быстрее и проще. Вы можете определить структурирование своих данных, а затем легко записывать и читать структурированные данные на разных языках в различных потоках данных, используя специально сгенерированный исходный код.

Адрес официального сайта:Developers.Google.com/protocol - нет...

использовать

Использование здесь только вводит использование Java. Конкретное использование protobuf3 можно увидетьРуководство по языку Protobuf (proto3). Сначала нам нужно создать папку proto в папке src/main, а затем создать новую в этой папке.user.protoфайл, этот файл определяет файл, который нам нужно передать.

Примечание: Когда **.proto** компилируется с помощью grpc, файлы protobuf в папке src/main/proto будут сканироваться по умолчанию.

Например, нам нужно определить информацию о пользователе, включая основные поля идентификатора, имени и возраста. затем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, После ввода нажмите Enter, чтобы увидеть сгенерированный файл 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,8Netty:: 4.0 или выше (кроме 5)Protobuf: 3.0 или выше

Если вы не знакомы с Netty, вы можете взглянуть на подробное объяснение telnet HelloWorld от netty, которое я написал ранее. Боже, пожалуйста, не обращайте внимания~. ~ адрес:блог woo woo woo.cn на.com/Sanshenghu…

Прежде всего, связанные зависимости 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, поэтому объект был непосредственно новым. После интеграции со 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!");

    }

}

После того, как основной класс сервера написан, зададим соответствующие условия фильтра. Здесь вам нужно наследоваться от NettyChannelInitializerкласс, затем переопределить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Основная причина в том, что несколько обработчиков могут безопасно совместно использоваться несколькими каналами, то есть для обеспечения безопасности потоков. Без лишних слов, код выглядит следующим образом:

    @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

вывод телнета:

Как показано ниже:

разное

О netty интеграции springboot и использовании protobuf для передачи данных здесь.

Netty интегрирует springboot и использует protobuf для передачи данных Адрес проекта проекта:GitHub.com/Саншэншу…

Кстати, есть и другие адреса проектов промежуточного программного обеспечения, интегрированные Netty:GitHub.com/Саншэншу…

Оригинал не просто, если вы чувствуете себя хорошо, я надеюсь дать рекомендацию! Ваша поддержка - самая большая мотивация для моего письма!

Заявление об авторских правах: Автор: Му Шувэй

Источник блога сада:блог woo woo woo.cn на.com/Sanshenghu…

источник на гитхабе:GitHub.com/Саншэншу…    

Источник личного блога:sanshengshui.github.io/