Netty上手使用

下面是原始的,后来被我慢慢改造了下,最新可以访问
https://github.com/xiaoyunhe-coldcast/coldcast.git
直接上代码,不多逼逼。

(1)服务端

package com.coldcast.netty;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.coldcast.util.logUtil;

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;

/**
 * Title : Netty服务端
 * @{author} Administrator
 * @{date} 2020年3月31日
 * @{description} 
 */
@Service
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();
            logUtil.info("netty服务启动: [port:" + port + "]");
            // 等待服务器socket关闭
            // 应用程序会一直等待,直到channel关闭
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
        	logUtil.error("netty服务启动异常-" + e.getMessage());
        } finally {
            // 优雅的关闭服务端
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

核心就是创建了两个group,一个boss,一个work.
(2)客户端代码

package com.coldcast.netty;
import java.time.LocalDate;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.springframework.stereotype.Component;

import com.coldcast.util.logUtil;

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;


/**
 * Title : 客户端
 * @{author} Administrator
 * @{date} 2020年3月31日
 * @{description} 
 */
@NoArgsConstructor
@Component
public class NettyClient {

    private EventLoopGroup group = new NioEventLoopGroup();

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

    /**
     *@{author} 连接
     *@{date} 2020年4月1日
     *@{tags} @param ip
     *@{tags} @param port
     */
    public Bootstrap connect(String ip,int port) {
        Channel channel = null;
        Bootstrap bootstrap = new Bootstrap();
        try {
            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(ip, port).sync().channel();
            // 处理
            handler(channel);
            // 应用程序会一直等待,直到channel关闭
            channel.closeFuture().sync();
            return bootstrap;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
		return bootstrap;
    }

    /**
     *@{author} 业务逻辑
     *@{date} 2020年4月1日
     *@{tags} @param channel
     */
    private void handler(Channel channel) {
        //如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
        //本次任务执行完后下次的任务还需要延迟period时间后再执行
        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("====定时任务开始====");
                // 发送json字符串
                LocalDate now = LocalDate.now();
                String msg = "{\"time\":\""+now.toString()+"\",\"name\":\"admin\",\"age\":27}\n";
                channel.writeAndFlush(msg);
            }
        }, 2, 10, TimeUnit.SECONDS);
    	channel.writeAndFlush("cient开始处理业务");
    	System.out.println("开始处理业务");
    }
  
    
    /**
     *@{author} 发送消息
     *@{date} 2020年4月1日
     *@{tags} @param channel
     *@{tags} @param message
     *@{tags} @return
     */
   public String sendMessage(String ip, int port,String message){
    	try{
    		Bootstrap bootstrap = connect(ip, port);
    		Channel channel = bootstrap.connect(ip, port).sync().channel();
    		channel.writeAndFlush(message);
    		logUtil.info(message);
    		return "消息发送成功";
    	}catch (Exception e) {
    		e.printStackTrace();
			logUtil.error("发送消息失败");
			return "发送消息失败";
		}
    };
    
    
    

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

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

客户端创建一个group就行了,这里消息发送使用的channel,做的一个定时发送,一直发消息。
(3)心跳检测

package com.coldcast.netty;

import org.springframework.stereotype.Component;

import com.coldcast.util.logUtil;

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;

/**
 * Title : 服务端心跳检查
 * @{author} Administrator
 * @{date} 2020年3月31日
 * @{description} 
 */
@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.ALL_IDLE) {
                logUtil.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;
    }*/
}

这里的心跳检测用的就是消息的另一种形式,使用ChannelHandlerContext,起初是只监控读被我改成所有的了。
(4)NettyServerHandler 服务端处理业务类,

package com.coldcast.netty;
import java.net.InetSocketAddress;
import java.util.Map;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.coldcast.util.logUtil;

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


/**
 * Title : 服务端处理器
 * @{author} Administrator
 * @{date} 2020年3月31日
 * @{description} 
 */
@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) {
        logUtil.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);
            logUtil.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();
        logUtil.info("收到客户端[ip:" + clientIp + "]连接");
    }

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

(5)NettyClientHandler 客户端业务处理类

package com.coldcast.netty;

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


/**
 * Title : 客户端处理
 * @{author} Administrator
 * @{date} 2020年3月31日
 * @{description} 
 */
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();
    }

}

(6)NettyServerInitializer服务端初始化

package com.coldcast.netty;

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;


/**
 * Title : service初始化配置
 * @{author} Administrator
 * @{date} 2020年3月31日
 * @{description} 
 */
@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);
    }
    
}

用于解决编码问题。客户端同理

package com.coldcast.netty;

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;

/**
 * Title : 客户端通道初始化
 * @{author} Administrator
 * @{date} 2020年3月31日
 * @{description} 
 */
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());
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值