• netty+springboot+oracle+protobuf 搭建客户端服务端


    netty5已经不维护了,所以用netty4搭建项目

    一、创建俩个基础的springboot项目

    分别为netty-client和netty-server

    二、添加依赖

    客户端

    <?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>2.5.6</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.netty</groupId>
        <artifactId>netty-client</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>netty-client</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>
            <!-- spring aop -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.1.69.Final</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.16.22</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.tomcat</groupId>
                        <artifactId>tomcat-jdbc</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!--@ConfigurationProperties注解-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <!--热部署-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!-- 集成mybatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.1</version>
            </dependency>
            <!--pagehelper -->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>1.2.10</version>
            </dependency>
            <!--oracle驱动 -->
            <dependency>
                <groupId>com.oracle</groupId>
                <artifactId>ojdbc6</artifactId>
                <version>11.2.0.3</version>
            </dependency>
            <!-- HikariCP 连接池依赖,从父依赖获取额版本 -->
            <dependency>
                <groupId>com.zaxxer</groupId>
                <artifactId>HikariCP</artifactId>
            </dependency>
            <!-- apache commons加密解密工具类 -->
            <dependency>
                <groupId>commons-codec</groupId>
                <artifactId>commons-codec</artifactId>
                <version>1.10</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.google.protobuf</groupId>
                <artifactId>protobuf-java</artifactId>
                <version>3.19.1</version>
            </dependency>
            <dependency>
                <groupId>com.google.protobuf</groupId>
                <artifactId>protobuf-java-util</artifactId>
                <version>3.19.0</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <!--fork :  如果没有该项配置,devtools不会起作用,即应用不会restart -->
                        <fork>true</fork>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>

    服务端

    <?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>2.5.6</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.netty</groupId>
        <artifactId>netty-server</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>netty-server</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>
            <!-- spring aop -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.1.69.Final</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.16.22</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.tomcat</groupId>
                        <artifactId>tomcat-jdbc</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!--@ConfigurationProperties注解-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <!--热部署-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!-- 集成mybatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.1</version>
            </dependency>
            <!--pagehelper -->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>1.2.10</version>
            </dependency>
            <!--oracle驱动 -->
            <dependency>
                <groupId>com.oracle</groupId>
                <artifactId>ojdbc6</artifactId>
                <version>11.2.0.3</version>
            </dependency>
            <!-- HikariCP 连接池依赖,从父依赖获取额版本 -->
            <dependency>
                <groupId>com.zaxxer</groupId>
                <artifactId>HikariCP</artifactId>
            </dependency>
            <!-- apache commons加密解密工具类 -->
            <dependency>
                <groupId>commons-codec</groupId>
                <artifactId>commons-codec</artifactId>
                <version>1.10</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.google.protobuf</groupId>
                <artifactId>protobuf-java</artifactId>
                <version>3.19.1</version>
            </dependency>
            <dependency>
                <groupId>com.google.protobuf</groupId>
                <artifactId>protobuf-java-util</artifactId>
                <version>3.19.0</version>
            </dependency>
            <!--redis-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>

    三、yml配置

    客户端

    server:
      port: 8002
      servlet:
        context-path: /
    netty:
      client:
        host: 127.0.0.1
        port: 8081
    
    async:
      executor:
        thread:
          core_pool_size: 8
          max_pool_size: 8
          queue_capacity: 99999
          name:
            prefix: async-service-
    pagehelper:
     helperDialect: oracle
     reasonable: true
     supportMethodsArguments: true
     params: count=countSql
    mybatis:
      config-location:
        classpath: mybatis/mybatis-config.xml
      mapper-locations: mapper/*.xml
      type-aliases-package: com.netty.data
    # 打印sql
    logging:
      level:
        com.netty.mapper : debug
    spring:
      devtools:
        restart:
          enabled: false
    # 配置数据源信息
      datasource:                                           # 数据源的相关配置
        type: com.zaxxer.hikari.HikariDataSource          # 数据源类型:HikariCP
        driver-class-name: oracle.jdbc.driver.OracleDriver       # oracle驱动
        url: jdbc:oracle:thin:@127.0.0.1:1521/orcl
        username: root
        password: 123456
        hikari:
          connection-timeout: 30000        # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒
          minimum-idle: 5                  # 最小连接数
          maximum-pool-size: 20            # 最大连接数
          auto-commit: true                # 事务自动提交
          idle-timeout: 600000             # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
          pool-name: DateSourceHikariCP     # 连接池名字
          max-lifetime: 1800000             # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms
          connection-test-query: SELECT 1 FROM DUAL  # 连接测试语句

    服务端

    server:
      port: 8001
      servlet:
        context-path: /
    netty:
      server:
        host: 127.0.0.1
        port: 8081
    async:
      executor:
        thread:
          core_pool_size: 8
          max_pool_size: 8
          queue_capacity: 99999
          name:
            prefix: async-service-
    mybatis:
      config-location:
        classpath: mybatis/mybatis-config.xml
      mapper-locations: mapper/*.xml
      type-aliases-package: com.netty.data
    # 打印sql
    logging:
      level:
        com.netty.mapper : debug
    spring:
      devtools:
        restart:
          enabled: false
      # 配置数据源信息
      datasource:                                           # 数据源的相关配置
        type: com.zaxxer.hikari.HikariDataSource          # 数据源类型:HikariCP
        driver-class-name: oracle.jdbc.driver.OracleDriver       # oracle驱动
        url: jdbc:oracle:thin:@127.0.0.1:1521/orcl
        username: root
        password: 123456
        hikari:
          connection-timeout: 30000        # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒
          minimum-idle: 5                  # 最小连接数
          maximum-pool-size: 20            # 最大连接数
          auto-commit: true                # 事务自动提交
          idle-timeout: 600000             # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
          pool-name: DateSourceHikariCP     # 连接池名字
          max-lifetime: 1800000             # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms
          connection-test-query: SELECT 1 FROM DUAL  # 连接测试语句
      redis:
        host: 127.0.0.1
        port: 6379
        #password:
        timeout: 30000
        jedis:
          pool:
            max-active: 8
            max-idle: 8
            min-idle: 0
            max-wait: -1

    四、目录结构

     五、代码

    客户端netty代码

    NettyClient
    /**
     * uifuture.com
     * Copyright (C) 2013-2018 All Rights Reserved.
     */
    package com.netty.client;
    
    
    import com.netty.protobuf.ProtobufDecoder;
    import com.netty.protobuf.ProtobufEncoder;
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.timeout.IdleStateHandler;
    import org.springframework.stereotype.Component;
    
    import java.net.InetSocketAddress;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author
     */
    @Component
    public class NettyClient {
    
        public void start()  throws Exception{
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .option(ChannelOption.SO_KEEPALIVE, true)
                        .remoteAddress(new InetSocketAddress("127.0.0.1", 8081))
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch)
                                    throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
                                pipeline.addLast(new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS));
                                pipeline.addLast(new ProtobufEncoder());
                                pipeline.addLast(new ProtobufDecoder());
                                pipeline.addLast(new ClientChannelHandler());
                            }
                        });
    
                // 异步操作
                ChannelFuture future = null;
                // netty 启动时如果连接失败,会断线重连sync();
                future  = bootstrap.connect().addListener(new ConnectionListener()).sync();
                // 关闭客户端
                future.channel().closeFuture().sync();
            } finally {
                //优雅关闭
               group.shutdownGracefully().sync();
            }
        }
    }
    ConnectionListener
    package com.netty.client;
    
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelFutureListener;
    import org.springframework.stereotype.Component;
    
    /**
     * 负责监听启动时连接失败,重新连接功能
     * @author
     */
    @Component
    public class ConnectionListener implements ChannelFutureListener {
        @Override
        public void operationComplete(ChannelFuture channelFuture) throws Exception {
            if (!channelFuture.isSuccess()) {
                System.out.println("-------------服务端重新连接-----------------");
                Thread.sleep(5000);
                new NettyClient().start();
            } else {
                System.err.println("服务端链接成功...");
            }
        }
    }
    /**
     * uifuture.com
     * Copyright (C) 2013-2018 All Rights Reserved.
     */
    package com.netty.client;
    
    import com.netty.config.ChannelContainer;
    import com.netty.protobuf.MessageProto;
    import com.netty.util.SpringUtil;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelHandler;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.handler.timeout.IdleState;
    import io.netty.handler.timeout.IdleStateEvent;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    
    /**
     * @author
     */
    @Component
    @ChannelHandler.Sharable
    public class ClientChannelHandler extends ChannelInboundHandlerAdapter {
    
    
        /**
         * 通道注册
         *
         * @param ctx
         * @throws Exception
         */
        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            super.channelRegistered(ctx);
        }
    
        /**
         * 服务器的连接被建立后调用
         * 建立连接后该 channelActive() 方法被调用一次
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            System.out.println("建立连接时:"+new Date());
            ChannelContainer channelContainer = SpringUtil.getBean(ChannelContainer.class);
            channelContainer.setChannel(ctx.channel());
        }
    
        /**
         * 关闭连接时
         */
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            //使用过程中断线重连
            System.out.println("使用过程中服务端链接不上,开始重连操作...");
            Thread.sleep(1000);
            new NettyClient().start();
        }
    
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
            if (obj instanceof IdleStateEvent) {
                IdleStateEvent event = (IdleStateEvent) obj;
                //如果写通道处于空闲状态,就发送心跳命令
                if (IdleState.WRITER_IDLE.equals(event.state())) {
                    String socketString = ctx.channel().localAddress().toString();
                    MessageProto.Message ping = MessageProto.Message.newBuilder().setId(socketString).setContent("hello").build();
                    ctx.channel().writeAndFlush(ping);
                }
            }
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            MessageProto.Message message = (MessageProto.Message) msg;
            String socketString = ctx.channel().remoteAddress().toString();
            System.out.println("server:"+socketString+":"+message.getContent());
            ctx.fireChannelRead(msg);
        }
    
        /**
         * 捕获异常时调用
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx,
                                    Throwable cause)  throws Exception{
            //记录错误日志并关闭 channel
            super.exceptionCaught(ctx, cause);
            cause.printStackTrace();
            Channel channel = ctx.channel();
            if(channel.isActive()){
                ctx.close();
            }
        }
    
    }
    SpringUtil
    package com.netty.util;
    
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    @Component
    public class SpringUtil implements ApplicationContextAware {
    
    
        private static ApplicationContext applicationContext;
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            if(SpringUtil.applicationContext == null) {
                SpringUtil.applicationContext = applicationContext;
            }
        }
    
        /**获取applicationContext*/
        public static ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    
        /**通过name获取 Bean*/
        public static Object getBean(String name){
            return getApplicationContext().getBean(name);
        }
    
        /**通过class获取Bean*/
        public static <T> T getBean(Class<T> clazz){
            return getApplicationContext().getBean(clazz);
        }
    
        /**通过name,以及Clazz返回指定的Bean*/
        public static <T> T getBean(String name,Class<T> clazz) {
            return getApplicationContext().getBean(name, clazz);
        }
    }

    protobuf

    ProtobufDecoder
    package com.netty.protobuf;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.ByteToMessageDecoder;
    
    import java.util.List;
    
    public class ProtobufDecoder extends ByteToMessageDecoder {
    
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            // 标记一下当前的readIndex的位置
            in.markReaderIndex();
            // 判断包头长度
            if (in.readableBytes() < 2) {
                // 不够包头
                 return;
            }
            // 读取传送过来的消息的长度。
            int length = in.readUnsignedShort();
            // 长度如果小于0
            if (length < 0) {
                // 非法数据,关闭连接
                ctx.close();
            }
            // 读到的消息体长度如果小于传送过来的消息长度
            if (length > in.readableBytes()) {
                // 重置读取位置
                in.resetReaderIndex();
                return;
            }
            ByteBuf frame = Unpooled.buffer(length);
            in.readBytes(frame);
            try {
                byte[] inByte = frame.array();
                // 字节转成对象
                MessageProto.Message msg = MessageProto.Message.parseFrom(inByte);
                if (msg != null) {
                    // 获取业务消息头
                    out.add(msg);
                }
            } catch (Exception e) {
                System.out.println(ctx.channel().remoteAddress() + ",decode failed."+e.getCause());
            }
        }
    }
    ProtobufEncoder
    package com.netty.protobuf;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.MessageToByteEncoder;
    
    public class ProtobufEncoder extends MessageToByteEncoder<MessageProto.Message> {
        @Override
        protected void encode(ChannelHandlerContext channelHandlerContext, MessageProto.Message msg, ByteBuf out) throws Exception {
            // 将对象转换为byte
            byte[] bytes = msg.toByteArray();
            // 读取消息的长度
            int length = bytes.length;
            ByteBuf buf = Unpooled.buffer(2 + length);
            // 先将消息长度写入,也就是消息头
            buf.writeShort(length);
            // 消息体中包含我们要发送的数据
            buf.writeBytes(bytes);
            out.writeBytes(buf);
        }
    }

    Message.proto

    syntax = "proto3";
    option java_outer_classname = "MessageProto";
    message Message {
      string id = 1;
      string content = 2;
    }

    这文件需要protobuf工具编译和idea需要安装插件

     新版idea在这里安装这俩个插件

    旧的需要下载好插件配到idea中

    参考安装方法 https://www.freesion.com/article/39501394899/

    装好插件,添加这个,有就不用加了,这样Message.proto就可以高亮显示

    springboot启动类

    package com.netty;
    
    import com.netty.client.NettyClient;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Profile;
    import org.springframework.scheduling.annotation.EnableScheduling;
    
    @SpringBootApplication
    @EnableScheduling
    @MapperScan({"com.netty.mapper"})
    public class NettyClientApplication implements CommandLineRunner {
    
        public static void main(String[] args) throws Exception{
            SpringApplication.run(NettyClientApplication.class, args);
        }
    
        @Override
        public void run(String... args) throws Exception {
            new NettyClient().start();
        }
    }

    这样就可以启动了

    服务端主要这几个不同,其他都一样

    NettyServer
    package com.netty.server;
    
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.Channel;
    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 io.netty.util.concurrent.Future;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.PreDestroy;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @author
     */
    @Component
    public class NettyServer {
        /**
         * boss事件轮询线程组
         * 处理Accept连接事件的线程,这里线程数设置为1即可,netty处理链接事件默认为单线程,过度设置反而浪费cpu资源
         */
        private EventLoopGroup boss = new NioEventLoopGroup(1);
    
        /**
         * worker事件轮询线程组
         * 处理hadnler的工作线程,其实也就是处理IO读写 。线程数据默认为 CPU 核心数乘以2
         */
        private EventLoopGroup worker = new NioEventLoopGroup();
    
        /**
         * 存储client的channel
         * key:ip,value:Channel
         */
        public static Map<String, Channel> map = new ConcurrentHashMap<String, Channel>();
    
        @Autowired
        private ServerChannelInitializer serverChannelInitializer;
    
        @Value("${netty.server.port}")
        private Integer port;
    
        /**与客户端建立连接后得到的通道对象*/
        private Channel channel;
        /**
         * 设置服务端端口
         * @throws Exception
         */
        public  ChannelFuture start()  {
            ServerBootstrap bootstrap = new ServerBootstrap();
            //第1步定义两个线程组,用来处理客户端通道的accept和读写事件
            bootstrap.group(boss,worker)
                    //第2步绑定服务端通道
                    .channel(NioServerSocketChannel.class)
                    //用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
                    //用来初始化服务端可连接队列
                    //服务端处理客户端连接请求是按顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //第3步绑定handler,处理读写事件,ChannelInitializer是给通道初始化
                    .childHandler(serverChannelInitializer);
            // 绑定端口,同步等待成功
            ChannelFuture channelFuture1 = bootstrap.bind(port).syncUninterruptibly();
            if (channelFuture1 != null && channelFuture1.isSuccess()) {
                //获取通道
                channel = channelFuture1.channel();
                System.out.println("Netty 服务 启动 成功, port ="+ port);
            } else {
                System.out.println("Netty 服务 启动 失败");
            }
            return channelFuture1;
        }
    
    
        /**
         * 停止Netty tcp server服务
         */
        @PreDestroy
        public void destroy() {
            System.out.println("==========Netty服务退出,释放线程资源==========");
            if (channel != null) {
                channel.close();
            }
            try {
                Future<?> future = worker.shutdownGracefully().await();
                if (!future.isSuccess()) {
                    System.out.println("netty tcp workerGroup shutdown fail"+ future.cause());
                }
                Future<?> future1 = boss.shutdownGracefully().await();
                if (!future1.isSuccess()) {
                    System.out.println("netty tcp bossGroup shutdown fail {}"+future1.cause());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Netty服务关闭成功");
        }
    
    }
    ServerChannelHandler
    package com.netty.server;
    
    import com.netty.data.Msg;
    import com.netty.protobuf.MessageProto;
    import com.netty.service.TaskService;
    import com.netty.util.GsonUtil;
    import com.netty.util.SpringUtil;
    import io.netty.channel.ChannelHandler;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.handler.timeout.IdleState;
    import io.netty.handler.timeout.IdleStateEvent;
    import org.springframework.stereotype.Component;
    
    
    /**
     * @author
     */
    @Component
    @ChannelHandler.Sharable
    public class ServerChannelHandler extends ChannelInboundHandlerAdapter {
    
        private String beat = "hello";
        private int count = 0;
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception {
            MessageProto.Message message = (MessageProto.Message) obj;
            String socketString = ctx.channel().remoteAddress().toString();
            if(beat.equals(message.getContent())){
                System.out.println("client:"+socketString+":"+message.getContent());
                MessageProto.Message pong = MessageProto.Message.newBuilder().setContent("ok").build();
                ctx.writeAndFlush(pong);
            }else {
                count ++;
                System.out.println("服务端接收客户端"+socketString+"的数据:");
                System.out.println(count+","+message.getId()+","+message.getContent());
                String content = message.getContent();
                System.out.println(content);
                Msg msg = GsonUtil.GsonToBean(content, Msg.class);
                msg.setRemoteaddress(socketString);
                TaskService taskService = SpringUtil.getBean(TaskService.class);
                taskService.insertMsg(msg);
            }
        }
    
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
    
            String socketString = ctx.channel().remoteAddress().toString();
            if (obj instanceof IdleStateEvent) {
                IdleStateEvent event = (IdleStateEvent) obj;
                //如果读通道处于空闲状态,说明没有接收到心跳命令
                if (IdleState.READER_IDLE.equals(event.state())) {
                    if (event.state() == IdleState.READER_IDLE) {
                        System.out.println("客户端: " + socketString + " READER_IDLE 读超时");
                    } else if (event.state() == IdleState.WRITER_IDLE) {
                        System.out.println("客户端: " + socketString + " WRITER_IDLE 写超时");
                    } else if (event.state() == IdleState.ALL_IDLE) {
                        System.out.println("客户端: " + socketString + " ALL_IDLE 总超时");
                    }
                }
            } else {
                super.userEventTriggered(ctx, obj);
            }
        }
    
    
        /**
         * @Description 客户端连接时执行,将客户端信息保存到Map中
         * @param ctx
         **/
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("通道启动");
            super.channelActive(ctx);
            System.out.println("客户端 " + getRemoteAddress(ctx) + " 链接成功");
            //往channel map中添加channel信息
            NettyServer.map.put(getIPString(ctx), ctx.channel());
        }
    
        /**
         * @Description 客户端断开连接时执行,将客户端信息从Map中移除
         * @param ctx
         * @Date 2019/8/28 14:22
         * @Author wuyong
         * @return
         **/
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            //删除Channel Map中的失效Client
            System.out.println("移除客户端通道:"+getIPString(ctx));
            NettyServer.map.remove(getIPString(ctx));
            ctx.close();
    
        }
        /**
         * 异常处理
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    
    
        /**
         * 获取client对象:ip+port
         *
         * @param ctx
         * @return
         */
        public String getRemoteAddress(ChannelHandlerContext ctx) {
            String socketString = "";
            socketString = ctx.channel().remoteAddress().toString();
            return socketString;
        }
    
        /**
         * 获取client的ip
         *
         * @param ctx
         * @return
         */
        public String getIPString(ChannelHandlerContext ctx) {
            String ipString = "";
            String socketString = ctx.channel().remoteAddress().toString();
            int colonAt = socketString.indexOf(":");
            ipString = socketString.substring(1, colonAt);
            return ipString;
        }
    
    
    }
    ServerChannelInitializer
    package com.netty.server;
    
    
    import com.netty.protobuf.ProtobufDecoder;
    import com.netty.protobuf.ProtobufEncoder;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.socket.SocketChannel;
    
    import io.netty.handler.timeout.IdleStateHandler;
    import org.springframework.stereotype.Component;
    
    import java.util.concurrent.TimeUnit;
    
    @Component
    public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            ChannelPipeline pipeline = socketChannel.pipeline();
            pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.MINUTES));
            pipeline.addLast(new ProtobufDecoder());
            pipeline.addLast(new ProtobufEncoder());
            //自定义Handler
            pipeline.addLast(new ServerChannelHandler());
        }
    }

    启动类

    package com.netty;
    
    import com.netty.server.NettyServer;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @MapperScan({"com.netty.mapper"})
    public class NettyServerApplication implements CommandLineRunner {
    
        @Autowired
        private NettyServer nettyServer;
        public static void main(String[] args) {
            SpringApplication.run(NettyServerApplication.class, args);
        }
    
        @Override
        public void run(String... args) throws Exception {
            nettyServer.start();
        }
    
    }

    线程池配置类

    AsyncTaskExecutorConfig
    package com.netty.config;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * @author
     */
    @Configuration
    @EnableAsync
    public class AsyncTaskExecutorConfig {
    
        private static final Logger logger = LoggerFactory.getLogger(AsyncTaskExecutorConfig.class);
    
        @Value("${async.executor.thread.core_pool_size}")
        private int corePoolSize;
        @Value("${async.executor.thread.max_pool_size}")
        private int maxPoolSize;
        @Value("${async.executor.thread.queue_capacity}")
        private int queueCapacity;
        @Value("${async.executor.thread.name.prefix}")
        private String namePrefix;
    
        @Bean(name = "taskExecutor")
        public Executor taskExecutor() {
            logger.info("开启线程池=====taskExecutor====");
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            //配置核心线程数
            executor.setCorePoolSize(corePoolSize);
            //配置最大线程数
            executor.setMaxPoolSize(maxPoolSize);
            //配置队列大小
            executor.setQueueCapacity(queueCapacity);
            //配置线程池中的线程的名称前缀
            executor.setThreadNamePrefix(namePrefix);
    
            /**
             * 拒绝处理策略
             * CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
             * AbortPolicy():直接抛出异常。
             * DiscardPolicy():直接丢弃。
             * DiscardOldestPolicy():丢弃队列中最老的任务。
             */
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            //执行初始化
            executor.initialize();
            return executor;
        }
    }

    其他的剩余实体类,mapper,service 自己按需求写,经过测试感觉protobuf 这种数据协议传输比自定义的协议更快

    以上就是全部的测试代码,仅供参考

  • 相关阅读:
    linux就该这么学.pdf
    linux中shell编辑小技巧
    相关功能分享
    现代操作系统第三版高清.pdf中文版免费下载
    linux高性能服务器编程pdf免费下载
    git每次更新都需要输入账号密码,如何解决?
    Python 面向对象
    模块和包
    Python常用模块(collections、 time、 random、 os 、sys、序列化模块)
    内置函数和匿名函数(lambda)
  • 原文地址:https://www.cnblogs.com/h-c-g/p/15504821.html
Copyright © 2020-2023  润新知