Netty的简单使用【Tcp长连接】---随笔

导入Maven依赖
<dependency>
   <groupId>#maven.io-netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.33.Final</version>
    <type>pom</type>
</dependency>
编码实现
服务端

NettyServer.java


import com.framework.common.utils.thredpool.PushThreadPools;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;


/**
 * @author savesl
 */
@Component
public class NettyServer {
    /**
     * 日志
     */
    private static final Logger logger  = Logger.getLogger(NettyServer.class);
    private int port;
    private ServerSocketChannel serverSocketChannel;

    public synchronized void startServer(int port) {
        try {
            this.port = port;
            bind();
        }catch(Exception ex) {
            ex.printStackTrace();
            logger.error("NettyServer start failure: "+ ex.getMessage());
        }
    }

    private void bind() {
        PushThreadPools.exec.execute(() -> {
            //服务端要建立两个group,一个负责接收客户端的连接,一个负责处理数据传输
            //连接处理group
            EventLoopGroup boss = new NioEventLoopGroup(1);
            //事件处理group
            EventLoopGroup worker = new NioEventLoopGroup();
            ServerBootstrap bootstrap = new ServerBootstrap();
            //创建ServerBootstrap实例
            ServerBootstrap serverBootstrap=new ServerBootstrap();
            // 绑定处理group
            bootstrap.group(boss, worker)
                    //设置将要被实例化的ServerChannel类
                    .channel(NioServerSocketChannel.class)
                    ///标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    //有数据立即发送
                    .option(ChannelOption.TCP_NODELAY, true)
                    /// 是否启用心跳保活机机制
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
//                        .option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(65535))
                    //在ServerChannelInitializer中初始化ChannelPipeline责任链,并添加到serverBootstrap中
                    .childHandler(new ServerChannelInitializer());

            ChannelFuture future;
            try {
                //绑定端口后,开启监听
                future = bootstrap.bind(port).sync();
                if (future.isSuccess()) {
                    serverSocketChannel = (ServerSocketChannel) future.channel();
                    System.out.println("server start success,port:" + port);
                } else {
                    System.out.println("server start failure,port:" + port);
                }
                //等待服务监听端口关闭,就是由于这里会将线程阻塞,导致无法发送信息,所以我这里开了线程
                future.channel().closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
            }
            finally {
                //优雅地退出,释放线程池资源
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        });
    }

    public void sendMessage(Object msg){
        if(serverSocketChannel != null){
            serverSocketChannel.writeAndFlush(msg);
        }
    }

    public static void main(String[] args) {
        new NettyServer().startServer(7000);
    }
}

线程池工具类:

public class PushThreadPools {
    //核心线程池大小
    static final int corePoolSize = 5;
    //最大线程池大小
    static final int maximumPoolSize = 50;
    //线程最大空闲时间
    static final long keepAliveTime = 2000L;
    //时间单位
    static final TimeUnit unit = TimeUnit.MILLISECONDS;

    public static ExecutorService exec = new ThreadPoolExecutor(
            //	核心线程池大小
            corePoolSize,
            //最大线程池大小
            maximumPoolSize,
            //线程最大空闲时间
            keepAliveTime,
            //时间单位
            unit,
            //线程等待队列
            new LinkedBlockingQueue<Runnable>(64),
            //线程创建工厂
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r);
                }
            },
            //拒绝策略,这里直接抛出异常
            new ThreadPoolExecutor.AbortPolicy()
    );
}

ServerChannelInitializer.java

import com.framework.modules.api.netty.util.CodeUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;


/**
 * @author admin
 */
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //数据包结尾标识:【十六进制的0的字节数组】
        ByteBuf delimiter = Unpooled.copiedBuffer(CodeUtil.hexItr2Arr("00"));
        //设置数据包最大字节数,与结尾标识
        pipeline.addLast(new DelimiterBasedFrameDecoder(65535, delimiter));
        pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast("handler", new ServerHandler());
        System.out.println("Client:"+ch.remoteAddress() +"连接上");
    }
}

ServerHandler.java

import com.framework.modules.api.netty.util.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Arrays;

/**
 * @author admin
 */

@Component
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Autowired
    private Linux5SmallServiceUtil linux5SmallServiceUtil;

    public static ServerHandler serverHandler;

    public ServerHandler(){

    }

    @PostConstruct
    public void init(){
        serverHandler = this;
        serverHandler.linux5SmallServiceUtil = this.linux5SmallServiceUtil;
    }

    /**
     * 存储每一个客户端接入进来时的channel对象
     */
    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    public void addChannel(Channel channel){
        if(!channels.contains(channel)){
            System.out.println(channel.remoteAddress()+"加入连接组...");
            channels.add(channel);
        }
    }

    public void removeChanel(Channel channel){
        if(channels.contains(channel)){
            System.out.println(channel.remoteAddress()+"退出连接组...");
            channels.remove(channel);
        }
    }

    /**
     * 5.覆盖了 channelActive() 事件处理方法。服务端监听到客户端活动
     * 客户端与服务端创建连接的时候调用
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) { // (5)
        Channel incoming = ctx.channel();
        addChannel(incoming);
        System.out.println("Client:"+incoming.remoteAddress()+"连接开始....");
    }

    /**
     * 6.覆盖了 channelInactive() 事件处理方法。服务端监听到客户端不活动
     * 客户端与服务端断开连接时调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) { // (6)
        Channel incoming = ctx.channel();
        removeChanel(incoming);
        System.out.println("Client:"+incoming.remoteAddress()+"掉线....");
    }

    /**
     * 服务端接收客户端发送过来的数据结束之后调用
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
        System.out.println("Client:"+ctx.channel().remoteAddress()+"信息接收完毕...");
    }

    /**
     * 7.exceptionCaught() 事件处理方法是当出现 Throwable 对象才会被调用,即当 Netty 由于 IO
     * 错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来并且把关联的 channel 给关闭掉。
     * 然而这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。
     * 服务出现异常的时候调用
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (7)
        Channel incoming = ctx.channel();
        System.out.println("Client:"+incoming.remoteAddress()+"异常");
        // 当出现异常就关闭连接
        cause.printStackTrace();
        ctx.close();
    }

    /**
     *
     * 服务端处理客户端socket请求的核心方法,这里接收了客户端发来的信息
     * @param ctx
     * @param info
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object info) throws Exception {
    	//以下为业务处理逻辑
        RequestData requestData = RequestData.parsingJsonString(info.toString());
        ResponseData responseData = serverHandler.linux5SmallServiceUtil.responseController(requestData.getType(), requestData);
        String response = ResponseData.toJsonString(responseData);

        System.err.println(response);
        ByteBuf pingMessage = Unpooled.buffer();
        byte[] arr = response.getBytes("UTF-8");
        byte[] pix = CodeUtil.hexItr2Arr("00");
        byte[] result = Arrays.copyOf(arr, arr.length + pix.length);
        System.arraycopy(pix, 0, result, arr.length, pix.length);

        pingMessage.writeBytes(result);
        ctx.writeAndFlush(pingMessage);

//        Channel incoming = ctx.channel();
//        for (Channel channel : channels) {
//            if (channel == incoming){
//                channel.writeAndFlush(str);
//            }
//        }

    }

}

客户端

Client.java

public class Client {
    static EventLoopGroup group =null;
    static Bootstrap client =null;
    public static ChannelFuture future=null;
    static {
        group = new NioEventLoopGroup();
        client = new Bootstrap();
        client.group(group);
        client.channel(NioSocketChannel.class);
        client.option(ChannelOption.SO_KEEPALIVE,true);
        client.handler(new ClientChannelInitializer());
        try {
            future = client.connect("192.168.0.164", 7000).sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void sendMessage(String content) throws Exception{
        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            byte[] arr = content.getBytes("UTF-8");
            //将结尾标识【0的十六进制字节数组】追加到消息字节数组尾部
            byte[] pix = CodeUtil.hexItr2Arr("00");
            byte[] result = Arrays.copyOf(arr, arr.length + pix.length);
            System.arraycopy(pix, 0, result, arr.length, pix.length);
            future.channel().writeAndFlush(Unpooled.buffer().writeBytes(result));
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }


    public static void main(String[] args) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("version", "1.0");
        map.put("type", "rsync_person_req");
        map.put("sn", "'86E492248B780F1C'");
        map.put("seq", 47);
        Map<String, Object> data = new HashMap<>();
        data.put("person_type", 0);
        data.put("person_id", 58);
        map.put("data", data);

        JSONObject json = new JSONObject(map);
        sendMessage(json.toString());

//        for(int i=0;i<20;i++){
//            new Thread(new UserRequestThread(i)).start();//模拟多线程并发请求
//        }
    }



ClientChannelInitializer.java

public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline p = channel.pipeline();
         //数据包结尾标识:【十六进制的0的字节数组】
        ByteBuf delimiter = Unpooled.copiedBuffer(CodeUtil.hexItr2Arr("00"));
        //设置数据包最大字节数,与结尾标识
        p.addLast(new DelimiterBasedFrameDecoder(65535, delimiter));
        p.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        p.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
        p.addLast(new ClientHandler());
    }
}

ClientHandler.java

public class ClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("ClientHandler Active");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("--------");
        System.out.println("ClientHandler read Message:"+msg);
    }


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

}
使用过程中的常见问题:

Netty handler处理类使用@Autowired注入无效

问题产生的原因:ServerHandler是netty启动的时候new出来,并没有交给spring IOC托管
解决方案:使用spring的两个注解@Component及@PostConstruct

@Component大家都比较熟悉,就不做介绍了

@PostConstruct: 在方法上加该注解会在项目启动的时候执行该方法,即spring容器初始化的时候执行,它与构造函数及@Autowired的执行顺序为:构造函数 >> @Autowired >> @PostConstruct,由此看来当我们想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于注入的bean,那么就无法在构造函数中实现,为此可以使用@PostConstruct注解一个init方法来完成初始化,该方法会在bean注入完成后被自动调用。

编码实现

@Component
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Autowired
    private Linux5SmallServiceUtil linux5SmallServiceUtil;

    public static ServerHandler serverHandler;

    public ServerHandler(){

    }

    @PostConstruct
    public void init(){
        serverHandler = this;
        serverHandler.linux5SmallServiceUtil = this.linux5SmallServiceUtil;
    }

使用方式:

serverHandler.linux5SmallServiceUtil.responseController(requestData.getType(), requestData);

修改数据包结尾标识与最大字节长度
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值