Springboot Integrates Netty Initial Practice

Keywords: Netty Java Mybatis xml

Netty is a powerful communication framework, and has used MINA similar to him before. Although MINA has many functions, it needs to write its own encoder (filter data) when dealing with TCP's unpacking and sticking problem. Netty provides some methods to solve this problem. In this Demo, the author also uses MINA. One of them, as for the others, can be searched on the Internet or can view official documents. This paper mainly focuses on building projects. Don't say much, just go to the code.

First, let's prepare a project without integrating Netty, which consists of Springboot, Mybatis and MySQL. I'm sure you've already vomited about this kind of code, but the author still has to upload it, because the author will use Netty in it.

First, we need to introduce relevant jar packages, and the authors use Maven to manage these dependencies:

POM.XML configuration:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.15.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.mcd</groupId>
    <artifactId>netty</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>netty</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

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

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

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!-- Netty -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0.Alpha1</version>
        </dependency>

        <!-- MySql drive -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- Database connection pool -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <!-- Ignore test files when packaging -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

The Demo is released as a Jar package, using the Maven instruction mvn clean package, or using the Maven tool to package in IDEA, with the same effect.

The second step is to configure the application.yml file:

application.yml:

# Configure Tomcat's startup port, project name
server:
  port: 18080
  context-path: /netty

# configure log files
logging.config:
    classpath:/logback.xml

# File Upload and Download Size and Speed Configuration
spring:
  http:
  multipart:
  max-file-size: 1000Mb
  max-request-size: 1000Mb

# Database Connection Configuration
jdbc:
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/mcd_netty?useUnicode=true&characterEncoding=utf8&useSSL=false              
  username: root
  password: 123

# mybatis configuration
# mybatis global configuration file
mybatis_config_file: mybatis-config.xml
# mybatis mapping file path
mapper_path: /mapper/**.xml
# mybatis Entity Package Path Configuration
entity_package: com.mcd.netty.entity

The configuration logback.xml of the log file is loaded in the yml configuration file and the global configuration file mybatis-config.xml of Mybatis is loaded in the yml configuration file.

logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- Define parameter constants -->
    <!-- TRACE<DEBUG<INFO<WARN<ERROR -->
    <!-- logger.trace("msg") logger.debug... -->
    <property name="log.level" value="debug"/>
    <property name="log.maxHistory" value="30"/>
    <property name="log.filePath" value="F:/ideaProject/saleProject/netty/log"/>
    <property name="log.pattern"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
    <!-- Console settings -->
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
    <!-- DEBUG -->
    <appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- File path -->
        <file>${log.filePath}/debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- File name -->
            <fileNamePattern>${log.filePath}/debug/debug.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>
            <!-- Maximum number of saved history files -->
            <maxHistory>${log.maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- INFO -->
    <appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- File path -->
        <file>${log.filePath}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- File name -->
            <fileNamePattern>${log.filePath}/info/info.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>
            <!-- Maximum number of saved history files -->
            <maxHistory>${log.maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- ERROR -->
    <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- File path -->
        <file>${log.filePath}/erorr.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- File name -->
            <fileNamePattern>${log.filePath}/error/error.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>
            <!-- Maximum number of saved history files -->
            <maxHistory>${log.maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <logger name="com.mcd.netty" level="${log.level}" additivity="true">
        <appender-ref ref="debugAppender"/>
        <appender-ref ref="infoAppender"/>
        <appender-ref ref="errorAppender"/>
    </logger>
    <root level="info">
        <appender-ref ref="consoleAppender"/>
    </root>
</configuration>

mybatis-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- Configure global properties -->
    <settings>
        <!-- Use jdbc Of getGeneratedKeys Getting database self-increasing primary key values -->
        <setting name="useGeneratedKeys" value="true"/>

        <!-- Replace column aliases with column labels by default: true -->
        <setting name="useColumnLabel" value="true"/>

        <!-- Begin hump naming conversion: Table{create_time} -> Entity{createTime} -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

Configuration files have been completed, and then some configurations are loaded. The author divides the configurations into cross-domain, Dao and transaction, and creates config packages under the basic package path, which include three layers: web, service and dao; cross-domain configurations are completed in web, transaction configurations are completed in service, and database is completed in dao. Relevant configuration. The documents are as follows:

CorsConfig.java:

package com.mcd.netty.config.web;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
* @Description: Configure external interface access settings
* @Author: amao
* @CreateDate: 2018/12/24 15:57
*/
//Indicates that this is a configuration class
@Configuration
public class CorsConfig {


    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOrigin("*"); // 1
        corsConfiguration.addAllowedHeader("*"); // 2
        corsConfiguration.addAllowedMethod("*"); // 3
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig()); // 4
        return new CorsFilter(source);
    }
}
TransactionManagementConfiguration.java:
package com.mcd.netty.config.service;//package com.mcd.netty.config.service;


import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.jdbc.datasource.DataSourceTransactionManager;
        import org.springframework.transaction.PlatformTransactionManager;
        import org.springframework.transaction.annotation.EnableTransactionManagement;
        import org.springframework.transaction.annotation.TransactionManagementConfigurer;

        import javax.sql.DataSource;

/**
 * @Description: Transaction configuration class
 * @Author: amao
 * @CreateDate: 2018/10/18 16:43
 */
@Configuration
@EnableTransactionManagement
public class TransactionManagementConfiguration implements TransactionManagementConfigurer {

    @Autowired
    private DataSource dataSource;

    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }
}
DataSourceConfiguration.java:
package com.mcd.netty.config.dao;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.beans.PropertyVetoException;

/**
* @Description: Database Connection Configuration
* @Author: amao
* @CreateDate: 2019/1/15 10:48
*/
//Indicates that this is a configuration class
@Configuration
//Configure the scan path of mybatis mapper
@MapperScan("com.mcd.netty.dao")
public class DataSourceConfiguration {
    //Reference to yml file driver type
    @Value("${jdbc.driver}")
    private String jdbcDriver;
    //Referencing the yml file database path
    @Value("${jdbc.url}")
    private String jdbcUrl;
    //Reference to yml file username
    @Value("${jdbc.username}")
    private String jdbcUsername;
    //Reference to yml file password
    @Value("${jdbc.password}")
    private String jdbcPassword;

    @Bean(name = "dataSource")
    public ComboPooledDataSource createDataSource() throws PropertyVetoException {
        //Create dataSouce
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        //Setting Driver Type
        dataSource.setDriverClass(jdbcDriver);
        //Setting up the database path
        dataSource.setJdbcUrl(jdbcUrl);
        //Username
        dataSource.setUser(jdbcUsername);
        //Set password
        dataSource.setPassword(jdbcPassword);
        // Do not commit automatically after closing the connection
        dataSource.setAutoCommitOnClose(false);

        return dataSource;
    }
}
SessionFactoryConfiguration.java:
package com.mcd.netty.config.dao;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.io.IOException;

/**
* @Description: Database Session Configuration
* @Author: amao
* @CreateDate: 2019/1/15 11:04
*/
//Indicates that this is a configuration class
@Configuration
public class SessionFactoryConfiguration {
    //Read mybatis global configuration file
    @Value("${mybatis_config_file}")
    private String mybatisConfigFilePath;
    //Read database settings
    @Qualifier("dataSource")
    @Autowired
    private DataSource dataSource;
    //Reading Entity Class Packet Path
    @Value("${entity_package}")
    private String entityPackage;
    //The path to read the mapping file
    @Value("${mapper_path}")
    private String mapperPath;

    //Creating Sessions Using spring Factory Classes
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactoryBean createSqlSessionFactoryBean() throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(mybatisConfigFilePath));
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        String packageSearchPath = PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + mapperPath;
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources(packageSearchPath));
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage(entityPackage);
        return sqlSessionFactoryBean;
    }
}

When these configurations are completed, you can start the project, but the console will print a warning message to remind you that there is no xml file under the mapper folder. At this time, we need to complete some basic CRUD. There are not too many explanations here, go directly to the code, students who do not understand can go to study first and read later. This paper:

 controller.NettyController.java:

package com.mcd.netty.controller;

import com.mcd.netty.service.NettyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/netty")
public class NettyController {
    private static final Logger log = LoggerFactory.getLogger(NettyController.class);

    @Autowired
    private NettyService nettyService;

    @RequestMapping(value = "/getMessage")
    public Map<String,Object> getMessage(){
        log.info("test");
        Map<String,Object> modelMap = new HashMap<>();
        modelMap.put("message", nettyService.getAllFacility());
        return modelMap;
    }

}

service.NettyService.java:

package com.mcd.netty.service;

import com.mcd.netty.entity.NettyMsg;

import java.util.List;

public interface NettyService {


    boolean saveMessage(NettyMsg nettyMsg);

    List<NettyMsg> getAllFacility();
}

service.impl.NettyServiceImpl.java:

package com.mcd.netty.service.impl;

import com.mcd.netty.dao.NettyDao;
import com.mcd.netty.entity.NettyMsg;
import com.mcd.netty.service.NettyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;

@Service
public class NettyServiceImpl implements NettyService {

    private static final Logger log = LoggerFactory.getLogger(NettyServiceImpl.class);

    @Autowired
    private NettyDao nettyDao;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveMessage(NettyMsg nettyMsg) {
        try {

            nettyMsg.setSaveTime(new Date());

            // 1. Judging whether there is data
            if (nettyDao.getMessageByFacilityId(nettyMsg.getFacilityId()) == null) {
                if (nettyDao.saveMessage(nettyMsg) != 1) {
                    throw new RuntimeException("Storage failure");
                }
            } else if (nettyDao.updateMessage(nettyMsg) != 1) {
                throw new RuntimeException("Update failed");
            }

            return true;
        } catch (RuntimeException e) {
            log.error(e.getMessage());
            throw new RuntimeException("Data Storage Failure");
        }
    }

    @Override
    public List<NettyMsg> getAllFacility() {
        return nettyDao.getAllFacility();
    }
}

dao.NettyDao.java:

package com.mcd.netty.dao;

import com.mcd.netty.entity.NettyMsg;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface NettyDao {
    NettyMsg getMessageByFacilityId(@Param("facilityId") String facilityId);

    int saveMessage(NettyMsg nettyMsg);

    int updateMessage(NettyMsg nettyMsg);

    List<NettyMsg> getAllFacility();
}

mapper/NettyDao.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mcd.netty.dao.NettyDao">
    <!-- Java Entity classes bind to database table fields -->
    <resultMap id="msgMap" type="com.mcd.netty.entity.NettyMsg">
        <!-- Equipment number -->
        <result property="facilityId" column="FACILITY_ID"/>
        <!-- Device name -->
        <result property="facilityName" column="FACILITY_NAME"/>
        <!-- longitude -->
        <result property="longitude" column="LONGITUDE"/>
        <!-- latitude -->
        <result property="latitude" column="LATITUDE"/>
        <!-- Altitude -->
        <result property="height" column="HEIGHT"/>
        <!-- RTK state -->
        <result property="RTKStatus" column="RTK_STATUS"/>
        <!-- Satellite number -->
        <result property="satelliteCount" column="SATELLITE_COUNT"/>
        <!-- Warehousing time -->
        <result property="saveTime" column="SAVE_TIME"/>
    </resultMap>

    <select id="getMessageByFacilityId" resultMap="msgMap">
        SELECT * FROM t_netty_msg WHERE FACILITY_ID = #{facilityId}
    </select>

    <insert id="saveMessage" parameterType="com.mcd.netty.entity.NettyMsg">
        INSERT INTO t_netty_msg
        (FACILITY_ID, FACILITY_NAME, LONGITUDE, LATITUDE,
         HEIGHT, RTK_STATUS, SATELLITE_COUNT, SAVE_TIME)
        VALUES
        (#{facilityId},#{facilityName},#{longitude},#{latitude},
        #{height},#{RTKStatus},#{satelliteCount},#{saveTime})
    </insert>

    <update id="updateMessage" parameterType="com.mcd.netty.entity.NettyMsg">
        UPDATE t_netty_msg
        <set>
            <if test="facilityName != null">FACILITY_NAME = #{facilityName},</if>
            <if test="longitude != null">LONGITUDE = #{longitude},</if>
            <if test="latitude != null">LATITUDE = #{latitude},</if>
            <if test="height != null">HEIGHT = #{height},</if>
            <if test="RTKStatus != null">RTK_STATUS = #{RTKStatus},</if>
            <if test="satelliteCount != null">SATELLITE_COUNT = #{satelliteCount},</if>
            <if test="saveTime != null">SAVE_TIME = #{saveTime},</if>
        </set>
        WHERE FACILITY_ID = #{facilityId}
    </update>

    <select id="getAllFacility" resultMap="msgMap">
        SELECT * FROM t_netty_msg
    </select>
</mapper>

entity.NettyMsg.java:

package com.mcd.netty.entity;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.util.Date;

public class NettyMsg {

    // Use UUID for device number
    private String facilityId;
    // Device name
    private String facilityName;
    // longitude
    private double longitude;
    // latitude
    private double latitude;
    // Altitude
    private double height;
    // RTK states such as 4,5
    private int RTKStatus;
    // Satellite number
    private int satelliteCount;
    // Warehousing time
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private Date saveTime;

    public String getFacilityId() {
        return facilityId;
    }

    public void setFacilityId(String facilityId) {
        this.facilityId = facilityId;
    }

    public String getFacilityName() {
        return facilityName;
    }

    public void setFacilityName(String facilityName) {
        this.facilityName = facilityName;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public int getRTKStatus() {
        return RTKStatus;
    }

    public void setRTKStatus(int RTKStatus) {
        this.RTKStatus = RTKStatus;
    }

    public int getSatelliteCount() {
        return satelliteCount;
    }

    public void setSatelliteCount(int satelliteCount) {
        this.satelliteCount = satelliteCount;
    }

    public Date getSaveTime() {
        return saveTime;
    }

    public void setSaveTime(Date saveTime) {
        this.saveTime = saveTime;
    }

    @Override
    public String toString() {
        return "Message{" +
                "facilityId='" + facilityId + '\'' +
                ", facilityName='" + facilityName + '\'' +
                ", longitude=" + longitude +
                ", latitude=" + latitude +
                ", height=" + height +
                ", RTKStatus=" + RTKStatus +
                ", satelliteCount=" + satelliteCount +
                ", saveTime=" + saveTime +
                '}';
    }
}

Using IDEA shortcuts, lombok can also be introduced to reduce the writing of getter/setter methods and so on. At this point, the basic process is over and the Web interface can now be accessed through various Client s (e.g. Browser, Mobile Phone, etc.).

Next, enter the highlight of this article=> Netty, before reading this article, need to understand the use of Netty, Netty in this Demo is managed by spring, directly on the code:

DiscardServer.java: 
package com.mcd.netty.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class DiscardServer {

    private static final Logger log = LoggerFactory.getLogger(DiscardServer.class);

    @Autowired
    private ChildChannelHandler childChannelHandler;

    public void run(int port) {

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        System.out.println("Ready to run port:" + port);
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .option(ChannelOption.SO_REUSEADDR, true) //Allow reuse of ports
                    .childHandler(childChannelHandler);
            //Bind ports, wait for success synchronously
            ChannelFuture f = bootstrap.bind(port).sync();
            //Waiting for the service listening port to close
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error(e.getMessage());
        } finally {
            //Exit, release thread resources
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

This Java file contains the whole process of Netty. What needs to be changed by ourselves is the processor in the childHandler method. We inject the object into it automatically, and then ChildChannelHandler.java:

package com.mcd.netty.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

    @Autowired
    private DiscardServerHandler discardServerHandler;

    public void initChannel(SocketChannel socketChannel) throws Exception {
        // Define the delimiter as $$(end-of-string splitting)
        ByteBuf delimiter = Unpooled.copiedBuffer("$$".getBytes());
        // Add a separator decoder to solve the problem of unpacking and sticking packages by separators
        socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(2048, delimiter));
        // Custom decoder for data acquisition and persistence processing
        socketChannel.pipeline().addLast(discardServerHandler);
    }
}

In this paper, I define two coders, one is to solve the problem of TCP unpacking and sticking, the other is to convert data into corresponding entity classes and do persistence processing. Delimiter BasedFrameDecoder is a decoder provided by Netty, which divides data according to the separator at the end of the message to solve the problem of sticky package. Other Netty decoders are not explained here, and they are similar in usage.

Next up is the custom decoder DiscardServerHandler.java:

package com.mcd.netty.netty;

import com.mcd.netty.entity.NettyMsg;
import com.mcd.netty.service.NettyService;
import com.mcd.netty.util.NettyUtils;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@ChannelHandler.Sharable
public class DiscardServerHandler extends ChannelHandlerAdapter {

    private static final Logger log = LoggerFactory.getLogger(DiscardServerHandler.class);

    @Autowired
    private NettyService nettyService;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            // 1. Data is split by delimiters
            String[] dataArray = NettyUtils.splitMsg(msg, ",");
            if (dataArray == null) {
                NettyUtils.write(ctx, NettyUtils.WRITE_FAIL_MSG);
            }
            for (String s : dataArray) {
                System.out.println(s);
            }
            // 2. Binding objects
            NettyMsg nettyMsg = NettyUtils.bindObject(NettyMsg.class,dataArray);
            // 3. Storing data and successfully returning client messages
            if(!nettyService.saveMessage(nettyMsg)){
                NettyUtils.write(ctx, NettyUtils.WRITE_SUCCESS_MSG);
            }
            NettyUtils.write(ctx, NettyUtils.WRITE_FAIL_MSG);

        } catch (RuntimeException e) {
            log.error(e.getMessage());
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // Close if abnormal happens
        log.error(cause.getMessage());
        ctx.close();
    }


}

I encapsulated some similar parts and made them into tool classes. Reference is as follows. Because the author is not very good at reflective application, you can rewrite the binding object method by yourselves.

NettyUtils.java:

package com.mcd.netty.util;

import com.mcd.netty.entity.NettyMsg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NettyUtils {

    private static final Logger log = LoggerFactory.getLogger(NettyUtils.class);

    public static final String WRITE_SUCCESS_MSG = "received success";

    public static final String WRITE_FAIL_MSG = "received fail";


    public static String[] splitMsg(Object msg, String regex) {

        String message = ((ByteBuf) msg).toString(CharsetUtil.UTF_8);

        if ("".equals(message) || message == null) {
            return null;
        }
        return message.split(regex);
    }

    public static void write(ChannelHandlerContext ctx, String msg) {
        ctx.write(
                ctx.alloc().buffer(4 * msg.length())
                        .writeBytes(msg.getBytes())
        );
        ctx.flush();
    }

    public static NettyMsg bindObject(Class nettyMsgName, String[] dataArray) {
        try {
            Class clazz = Class.forName(nettyMsgName.getName());
            NettyMsg nettyMsg = (NettyMsg) clazz.newInstance();
            nettyMsg.setFacilityId(dataArray[0]);
            nettyMsg.setFacilityName(dataArray[1]);
            nettyMsg.setLongitude(Double.parseDouble(dataArray[2]));
            nettyMsg.setLatitude(Double.parseDouble(dataArray[3]));
            nettyMsg.setHeight(Double.parseDouble(dataArray[4]));
            nettyMsg.setRTKStatus(Integer.parseInt(dataArray[5]));
            nettyMsg.setSatelliteCount(Integer.parseInt(dataArray[6]));
            return nettyMsg;
        } catch (ClassNotFoundException e) {
            log.error(e.getMessage());
        } catch (IllegalAccessException e) {
            log.error(e.getMessage());
        } catch (InstantiationException e) {
            log.error(e.getMessage());
        }
        return null;
    }


}

Next is the startup class, where Tomcat and Netty's TCP services are started at the same time. The code is as follows:

NettyApplication.java:

package com.mcd.netty;

import com.mcd.netty.netty.DiscardServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class NettyApplication implements CommandLineRunner {

    @Autowired
    private DiscardServer discardServer;

    public static void main(String[] args) {
        SpringApplication.run(NettyApplication.class, args);
    }

    @Override
    public void run(String... strings) throws Exception {
        discardServer.run(28080);
    }
}

At this point, you can start the project (students who don't know CommandLine Runner can search for information online), start a network debugging assistant locally, send data, and then start a Chrome, call the interface, you can see that the data is updated in real time. So far, the basic functions have been realized.

 

CSDN Download Link: https://download.csdn.net/download/qq_35062384/11720368

GitHub address: https://github.com/amaohaoshuai/netty_demo

Posted by Blicka on Wed, 11 Sep 2019 07:05:23 -0700