【弄nèng - Netty】Springboot整合Netty

本片以使用为主,概念请移动百度
不做过多解释,备注的很详细

项目基于SpringBoot

1. pom

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--netty-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.31.Final</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--优化编码-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>

2. 服务端

核心类 NettyServer.java

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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * @author 司马缸砸缸了
 * @description NETTY服务端
 * @date 2019/8/1 10:10
 */
@Service
@Slf4j
public class NettyServer {
    //boss事件轮询线程组 ,处理连接事件
    private EventLoopGroup bossGroup = new NioEventLoopGroup();
    //worker事件轮询线程组, 用于数据处理
    private EventLoopGroup workerGroup = new NioEventLoopGroup();

    @Autowired
    private NettyServerInitializer nettyServerInitializer;

    @Value("${netty.port}")
    private Integer port;

    /**
     * 开启Netty服务
     *
     * @return
     */
    public void start() {
        try {
            //启动类
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //设置参数,组配置
            serverBootstrap.group(bossGroup, workerGroup)
                    //socket参数,当服务器请求处理程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
                    // 如果未设置或所设置的值小于1,Java将使用默认值50。
                    //
                    // 服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                    // 建议长连接的时候打开,心跳检测
                    // .childOption(ChannelOption.SO_KEEPALIVE, true)

                    // 构造channel通道工厂//bossGroup的通道,只是负责连接
                    .channel(NioServerSocketChannel.class)
                    // 设置通道处理者ChannelHandlerworkerGroup的处理器
                    .childHandler(nettyServerInitializer);
            // 绑定端口,开始接收进来的连接
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            log.info("netty服务启动: [port:" + port + "]");
            // 等待服务器socket关闭
            // 应用程序会一直等待,直到channel关闭
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("netty服务启动异常-" + e.getMessage());
        } finally {
            // 优雅的关闭服务端
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

初始化类 NettyServerInitializer.java

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

/**
 * @author 司马缸砸缸了
 * @description service初始化配置
 * @date 2019/8/1 10:30
 */

@Component
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    private NettyServerHandler nettyServerHandler;

    @Autowired
    private AcceptorIdleStateTrigger idleStateTrigger;
    /**
     * 初始化channel
     */
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 分隔符解码器,处理半包。
        // maxFrameLength 表示一行最大的长度
        // Delimiters.lineDelimiter(),以/n,/r/n作为分隔符
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        // 自定义心跳检测
        // 1)readerIdleTime:为读超时时间(即多长时间没有接受到客户端发送数据)
        // 2)writerIdleTime:为写超时时间(即多长时间没有向客户端发送数据)
        // 3)allIdleTime:所有类型的超时时间
        pipeline.addLast(new IdleStateHandler(5,0,0, TimeUnit.SECONDS));
        ch.pipeline().addLast(idleStateTrigger);

        pipeline.addLast(nettyServerHandler);
    }
    
}

处理类 NettyServerHandler.java

import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.EventExecutorGroup;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.util.Map;

/**
 * @author 司马缸砸缸了
 * @description 服务端处理器
 * @date 2019/8/1 10:42
 */
@Slf4j
@Component
// 这个注解适用于标注一个channel handler可以被多个channel安全地共享
// 也可以使用new NettyServerHandler()方式解决
@ChannelHandler.Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {


    /**
     * String 也可以是Object类型
     *
     * @param ctx
     * @param msg
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) {
        log.info("接收到的消息:{}", msg);
        StringBuilder sb = null;
        Map<String, Object> result = null;
        try {
            // 报文解析处理
            sb = new StringBuilder();
            result = JSON.parseObject(msg);

            sb.append(result);
            sb.append("解析成功");
            sb.append("\n");
            ctx.writeAndFlush(sb);
        } catch (Exception e) {
            String errorCode = "-1\n";
            ctx.writeAndFlush(errorCode);
            log.error("报文解析失败: " + e.getMessage());
        }
    }

    /**
     * 客户端去和服务端连接成功时触发
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        log.info("收到客户端[ip:" + clientIp + "]连接");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 当出现异常就关闭连接
        ctx.close();
        //把客户端的通道关闭
        ctx.channel().close();
    }
    
}

心跳配置类 AcceptorIdleStateTrigger.java

import io.netty.channel.ChannelFutureListener;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author yangyang7_kzx
 * @date 2019/8/1 17:51
 * @description 服务端心跳检查
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class AcceptorIdleStateTrigger extends ChannelInboundHandlerAdapter {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            if (idleStateEvent.state() == IdleState.READER_IDLE) {
                log.info("已经5秒未收到客户端的消息了!");
                //向服务端送心跳包
                String heartbeat = "{\"msg\":\"server heart beat\"}\n";
                //发送心跳消息,并在发送失败时关闭该连接
                ctx.writeAndFlush(heartbeat).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    /*
        //备注 , 状态
        switch (event.state()){
        case READER_IDLE:
            eventType = "读空闲";
            break;
        case WRITER_IDLE:
            eventType = "写空闲";
            break;
        case ALL_IDLE:
            eventType ="读写空闲";
            break;
    }*/
}

3. 客户端

核心类 NettyClient.java

import com.it.cloud.itagent.client.NettyClientInitializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.NoArgsConstructor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 多次发送消息
 *
 * @author 司马缸砸缸了
 * @description 客户端TEST
 * @date 2019/8/1 10:53
 */


@NoArgsConstructor
public class NettyClient {

    private EventLoopGroup group = new NioEventLoopGroup();

    // 定时线程池
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

    /**
     * 连接方法
     */
    public void connect() {
        Channel channel = null;
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class);
            // 将小的数据包包装成更大的帧进行传送,提高网络的负载,即TCP延迟传输
            bootstrap.option(ChannelOption.TCP_NODELAY, true);

            // 设置TCP的长连接,默认的 keepalive的心跳时间是两个小时
            // bootstrap.option(ChannelOption.SO_KEEPALIVE, true);

            bootstrap.handler(new NettyClientInitializer());
            channel = bootstrap.connect("127.0.0.1", 2222).sync().channel();
            // 处理
            handler(channel);
            // 应用程序会一直等待,直到channel关闭
            channel.closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

    /**
     * 业务逻辑
     *
     * @param channel
     */
    private void handler(Channel channel) {
        //如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
        //本次任务执行完后下次的任务还需要延迟period时间后再执行
        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("====定时任务开始====");
                // 发送json字符串
                String msg = "{\"name\":\"admin\",\"age\":27}\n";
                channel.writeAndFlush(msg);
            }
        }, 2, 10, TimeUnit.SECONDS);

    }

    /**
     * 主动关闭
     */
    public void close() {
        group.shutdownGracefully();
    }

    /**
     * 测试入口
     *
     * @param args
     */
    public static void main(String[] args) {
        NettyClient nettyClient = new NettyClient();
        nettyClient.connect();
    }

}

初始化类 NettyClientInitializer.java

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

/**
 * @author 司马缸砸缸了
 * @description 客户端通道初始化
 * @date 2019/8/1 10:57
 */
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {

    /**
     * 初始化channel
     */
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new NettyClientHandler());
    }
}

处理类 NettyClientHandler.java

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @author 司马缸砸缸了
 * @description 客户端处理
 * @date 2019/8/1 10:58
 */
public class NettyClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("收到服务端消息: " + msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

4. 服务端启动类

整合springboot,实现CommandLineRunner 重写run,启动应用时启动netty server

import com.it.cloud.itagent.server.NettyServer;
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 {

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

    @Autowired
    private NettyServer nettyServer;

    @Override
    public void run(String... args) {
        nettyServer.start();
    }
}

5. 测试

  1. 先启动Springboot,NettyApplication.java
  2. 再启动客户端NettyClient.java

服务端控制台:
在这里插入图片描述

客户端控制台:
在这里插入图片描述


源码地址

传送门
开源项目,持续不断更新中,喜欢请 Star~

项目推荐

IT-CLOUD :IT服务管理平台,集成基础服务,中间件服务,监控告警服务等

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值