Netty是一个功能强大的通信框架,以前也用过与他类似的MINA,MINA虽然功能很多,但在处理TCP的拆包粘包问题时,需要自己编写编码器(对数据进行过滤),而Netty则提供了一些方法来解决,在本次Demo中,作者也使用了其中的一种,至于其他的,可以上网上搜索,也可以查看官方文档。本文主要是以搭建项目为主。话不多说,直接上代码。

      首先我们先准备一个没有整合Netty的项目,该项目由Springboot、Mybatis以及MySQL构成。相信各位对于这种代码都已经看吐了,但作者还是要上传一下,因为作者会在当中穿插着使用Netty。

     第一步,我们需要引入相关的jar包,作者使用Maven来管理这些依赖:

POM.XML配置:

<?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驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- 数据库连接池 -->
        <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>

            <!-- 打包时忽略测试文件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

mvn clean package,也可以在IDEA中使用Maven工具进行打包,效果相同。

application.yml文件:

application.yml:

# 配置Tomcat的启动端口、项目名
server:
  port: 18080
  context-path: /netty

# 配置日志文件
logging.config:
    classpath:/logback.xml

# 文件上传下载大小、速度配置
spring:
  http:
  multipart:
  max-file-size: 1000Mb
  max-request-size: 1000Mb

# 数据库连接配置
jdbc:
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/mcd_netty?useUnicode=true&characterEncoding=utf8&useSSL=false              
  username: root
  password: 123

# mybatis配置
# mybatis全局配置文件
mybatis_config_file: mybatis-config.xml
# mybatis映射文件路径
mapper_path: /mapper/**.xml
# mybatis实体类包路径配置
entity_package: com.mcd.netty.entity

logback.xml以及Mybatis的全局配置文件mybatis-config.xml,在这里也一并放出,也可以参考网上的:

logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 定义参数常量 -->
    <!-- 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"/>
    <!-- 控制台设置 -->
    <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>${log.filePath}/debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 文件名称 -->
            <fileNamePattern>${log.filePath}/debug/debug.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>
            <!-- 文件最大保存历史数量 -->
            <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>${log.filePath}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 文件名称 -->
            <fileNamePattern>${log.filePath}/info/info.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>
            <!-- 文件最大保存历史数量 -->
            <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>${log.filePath}/erorr.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 文件名称 -->
            <fileNamePattern>${log.filePath}/error/error.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>
            <!-- 文件最大保存历史数量 -->
            <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>
    <!-- 配置全局属性 -->
    <settings>
        <!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 -->
        <setting name="useGeneratedKeys" value="true"/>

        <!-- 使用列标签替换列别名 默认:true -->
        <setting name="useColumnLabel" value="true"/>

        <!-- 开始驼峰命名转换:Table{create_time} -> Entity{createTime} -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

      配置文件已经全部写完了,接下来开始加载一些配置,作者在这里把配置分为了跨域、Dao和事务,在基础包路径下创建config包,其中包含web、service、dao三层;web中完成跨域的配置,service完成事务的配置,dao中完成数据库相关配置。文件如下:

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: 配置对外接口访问设置
* @Author: amao
* @CreateDate: 2018/12/24 15:57
*/
//表明这是一个配置类
@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: 事务配置类
 * @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: 数据库连接配置
* @Author: amao
* @CreateDate: 2019/1/15 10:48
*/
//表明这是一个配置类
@Configuration
//配置mybatis mapper的扫描路径
@MapperScan("com.mcd.netty.dao")
public class DataSourceConfiguration {
    //引用yml文件驱动类型
    @Value("${jdbc.driver}")
    private String jdbcDriver;
    //引用yml文件数据库路径
    @Value("${jdbc.url}")
    private String jdbcUrl;
    //引用yml文件用户名
    @Value("${jdbc.username}")
    private String jdbcUsername;
    //引用yml文件密码
    @Value("${jdbc.password}")
    private String jdbcPassword;

    @Bean(name = "dataSource")
    public ComboPooledDataSource createDataSource() throws PropertyVetoException {
        //创建dataSouce
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        //设置驱动类型
        dataSource.setDriverClass(jdbcDriver);
        //设置数据库路径
        dataSource.setJdbcUrl(jdbcUrl);
        //设置用户名
        dataSource.setUser(jdbcUsername);
        //设置密码
        dataSource.setPassword(jdbcPassword);
        // 关闭连接后不自动commit
        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: 数据库会话配置
* @Author: amao
* @CreateDate: 2019/1/15 11:04
*/
//表明这是一个配置类
@Configuration
public class SessionFactoryConfiguration {
    //读取mybatis全局配置文件
    @Value("${mybatis_config_file}")
    private String mybatisConfigFilePath;
    //读取数据库设置
    @Qualifier("dataSource")
    @Autowired
    private DataSource dataSource;
    //读取实体类包路径
    @Value("${entity_package}")
    private String entityPackage;
    //读取映射文件的路径
    @Value("${mapper_path}")
    private String mapperPath;

    //使用spring工厂类创建会话
    @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;
    }
}

        当这些配置完成后,就可以启动项目了,但是控制台会打印一条警告信息,提示你mapper文件夹下没有xml文件,这时候,我们需要完成一些基础的CRUD,这里就不过多解释了,直接上代码,不懂的同学可以先去学习后在看本文:

 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.判断是否有数据
            if (nettyDao.getMessageByFacilityId(nettyMsg.getFacilityId()) == null) {
                if (nettyDao.saveMessage(nettyMsg) != 1) {
                    throw new RuntimeException("存储失败");
                }
            } else if (nettyDao.updateMessage(nettyMsg) != 1) {
                throw new RuntimeException("更新失败");
            }

            return true;
        } catch (RuntimeException e) {
            log.error(e.getMessage());
            throw new RuntimeException("数据存储失败");
        }
    }

    @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实体类与数据库表字段绑定 -->
    <resultMap id="msgMap" type="com.mcd.netty.entity.NettyMsg">
        <!-- 设备编号 -->
        <result property="facilityId" column="FACILITY_ID"/>
        <!-- 设备名称 -->
        <result property="facilityName" column="FACILITY_NAME"/>
        <!-- 经度 -->
        <result property="longitude" column="LONGITUDE"/>
        <!-- 纬度 -->
        <result property="latitude" column="LATITUDE"/>
        <!-- 高程 -->
        <result property="height" column="HEIGHT"/>
        <!-- RTK状态 -->
        <result property="RTKStatus" column="RTK_STATUS"/>
        <!-- 卫星数量 -->
        <result property="satelliteCount" column="SATELLITE_COUNT"/>
        <!-- 入库时间 -->
        <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 {

    // 设备编号 使用UUID
    private String facilityId;
    // 设备名称
    private String facilityName;
    // 经度
    private double longitude;
    // 纬度
    private double latitude;
    // 高程
    private double height;
    // RTK状态 例如4,5
    private int RTKStatus;
    // 卫星数量
    private int satelliteCount;
    // 入库时间
    @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 +
                '}';
    }
}

       这里使用IDEA快捷键生成的,也可以引入lombok来减少getter/setter方法等的编写。到此,基本的流程已经结束了,现在可以通过各个Client(例如Browser,Mobile Phone等等)访问这个Web接口了。

      接下来,进入本文的重头戏 => Netty,在看本文前,需要了解Netty的用法,本Demo中Netty用spring来管理,直接上代码:

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("准备运行端口:" + port);
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .option(ChannelOption.SO_REUSEADDR, true) //允许重复使用端口
                    .childHandler(childChannelHandler);
            //绑定端口,同步等待成功
            ChannelFuture f = bootstrap.bind(port).sync();
            //等待服务监听端口关闭
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error(e.getMessage());
        } finally {
            //退出,释放线程资源
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

       该Java文件中包含了Netty的整体流程,需要自己动手更改的是childHandler方法中的处理器,我们这边给他自动注入对象,接下来就是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 {
        // 定义分隔符为$$(字符串末尾分割)
        ByteBuf delimiter = Unpooled.copiedBuffer("$$".getBytes());
        // 添加分隔符解码器,通过分隔符来解决拆包粘包的问题
        socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(2048, delimiter));
        // 自定义解码器,用来获取数据并做持久化处理
        socketChannel.pipeline().addLast(discardServerHandler);
    }
}

        这里面我定义了两个编码器,一个来解决TCP的拆包粘包问题,第二个用来把数据转换成对应的实体类,并做持久化处理。 DelimiterBasedFrameDecoder这个解码器是由Netty提供的,根据报文结尾的分隔符来分割数据,从而解决粘包的问题,其他Netty提供的解码器在这就不做讲解了,用法类似。

        接下里就是自定义解码器 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. 数据根据分隔符拆分
            String[] dataArray = NettyUtils.splitMsg(msg, ",");
            if (dataArray == null) {
                NettyUtils.write(ctx, NettyUtils.WRITE_FAIL_MSG);
            }
            for (String s : dataArray) {
                System.out.println(s);
            }
            // 2. 绑定对象
            NettyMsg nettyMsg = NettyUtils.bindObject(NettyMsg.class,dataArray);
            // 3. 存储数据,存储成功返回客户端消息
            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 {
        // 出现异常就关闭
        log.error(cause.getMessage());
        ctx.close();
    }


}

       里面一些比较相似的部分我进行了封装,做成了工具类,参考如下,由于作者基础较差,反射应用的不是很熟练,各位可以自己重写该绑定对象方法。

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;
    }


}

接下来是启动类,在这里面同时启动Tomcat服务和Netty的TCP服务,代码如下:

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);
    }
}

        到此,就可以启动项目了(不了解CommandLineRunner的同学可在网上搜索资料),在本地启动一个网络调试助手,发送数据,再启动一个Chrome,调用接口,可以看到数据实时在更新。到此,基础功能就实现了。