SpringBoot结合Netty实现网络编程

Netty简介

前段时间要实现与一些物联网设备的通信的需求,设备一般都是使用TCP协议进行通信,经过调研觉得Netty确实是实现这一需求的最好框架,经过一番研究觉得Netty这个框架确实太强大了,自己只是使用了其中的一部分功能,先记录一下以后慢慢品味吧。

Netty官网(https://netty.io/)给出的定义是:Netty 是 NIO 客户端/服务器框架,可快速轻松地开发网络应用程序,如协议服务器和客户端。它大大简化和简化了网络编程的难度,如实现一个TCP和UDP的服务端程序。

Netty原理

从官网的介绍可以看出Netty是基于NIO的实现,那么NIO又是什么呢?谈起NIO就不得不说他的前一代BIO(Blocking IO)

  • BIO(Blocking IO)阻塞式IO

通俗一点说就是一个客户端和一个服务端通信时同一时间内只能处理一个请求,此时如果有新请求,那么就被阻塞了,只有等待上一个请求完成了才能进行下一个请求的处理。这样的结构导致如果要实现多客户端的处理就要不断增加线程数。而线程数的增加会给上下文切换带来很大的开销,你想也是人多了不好管理就是这个道理。

SpringBoot结合Netty实现网络编程

BIO模型

  • NIO(Non Blocking IO)非阻塞式IO

从NIO的名字就可以知道NIO就是为了解决BIO的这种弊端而产生的,以下是NIO的模型

SpringBoot结合Netty实现网络编程

从图中可以看出NIO多了一个Selector大管家,所有的读写请求先到Selector报道,然后由它分配给相应的线程进行处理。这样就会避免大量线程的堆积,用少量的线程就可以进行大量连接的处理,线程复用了嘛。

那么Netty就是基于NIO的模型进一步封装而实现,再加之一些性能优化和面向对象的编程思想使之在易用性,高并发性能以及安全性上都有很大的提升,以下是Netty大致的工作流程以及组件。

SpringBoot结合Netty实现网络编程

netty工作流程图

BossGroup: 主线程组,主要负责调度

WorkerGroup: 工作线程组,主要是处理具体工作

Channel:管道,用来连接两端,它的实例可以是一个Socket

Handler:处理器,可以进行编解码啊,消息处理等等具体工作

Springboot整合Netty

  • 新建一个Springboot工程,并引入依赖
<dependency>
  	<groupId>io.netty</groupId>
		<artifactId>netty-all</artifactId>
		<version>4.1.17.Final</version>
</dependency>
  • 创建NettyServer
public class NettyServer implements Runnable{


    @Override
    public void run() {
      	//指定端口服务这里使用13001端口
        InetSocketAddress socketAddress = new InetSocketAddress( 13001);
        this.start(socketAddress);
    }

  	 /**
     * 开启服务
     * @param socketAddress
     */
    public void start(InetSocketAddress socketAddress) {

        //new 一个主线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //new 一个工作线程组
        EventLoopGroup workGroup = new NioEventLoopGroup(200);
        ServerBootstrap bootstrap = new ServerBootstrap()
                .group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ServerChannelInitializer())
                .localAddress(socketAddress)
                //设置队列大小
                .option(ChannelOption.SO_BACKLOG, 1024)
                // 两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        //绑定端口,开始接收进来的连接
        try {
            ChannelFuture future = bootstrap.bind(socketAddress).sync();
            log.info("服务器启动开始监听端口: {}", socketAddress.getPort());
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("服务器开启失败", e);
        } finally {
            //关闭主线程组
            bossGroup.shutdownGracefully();
            //关闭工作线程组
            workGroup.shutdownGracefully();
        }
    }
}

1、这里实现了Runnable接口单独起一个线程为了和springboot的端口分开

2、Netty服务的初始化ServerBootstrap采用Builder模式需要指定线程组,管道,管道处理器,端口以及一些其他参数,这里不再赘述。

3、bootstrap绑定后会有回调ChannelFuture,Future是再线程编程中非常常用也非常好用同学们有兴趣可以研究一下它的具体实现

  • Channel的实现NioServerSocketChannel
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //添加编解码
        socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        socketChannel.pipeline().addLast("encoder", new StringDecoder(CharsetUtil.UTF_8));
        //添加处理器
        socketChannel.pipeline().addLast(new NettyServerHandler());
    }
}

1、ServerBootstrap.init()方法中,负责accept新链接的Channel的pipeline被添加了一个ChannelInitializer

2、在此类中可以加入很多处理,这里只加了字符编解码器和自定义的处理器,还可以增加一个防止TCP的粘包和拆包的处理,这个以后再讲。

  • Handler的实现NettyServerHandler
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    public static Map<String, ChannelHandlerContext> ctxMap = new ConcurrentHashMap<String, ChannelHandlerContext>(16);

    /**
     * 客户端连接会触发
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        String clientIp = getClientIp(ctx);
        ctxMap.put(clientIp,ctx);
        log.info("有客户端进行连接:{}",clientIp);
    }

    /**
     * 客户端发消息会触发
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(ctx.channel().remoteAddress());
        String ip = getClientIp(ctx);
        System.out.printf("客户端IP:"+ip);
        String body = msg.toString();
        log.info("服务器收到消息: {}", body);
        ctx.flush();
    }


    /**
     * 发生异常触发
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 主动发送消息
     *
     * @param key
     * @param cmd
     */
    public static void sendMessage(String key,String cmd){
        ChannelHandlerContext ctx = ctxMap.get(key);
        if(null!=ctx){
            ctx.write(cmd);
            ctx.flush();
        }else{
            log.error("客户端已离线:"+key);
        }

    }

    /**
     * 获取客户端ip
     * @param ctx
     * @return
     */
    private String getClientIp(ChannelHandlerContext ctx){
        InetSocketAddress ipSocket = (InetSocketAddress)ctx.channel().remoteAddress();
        String clientIp = ipSocket.getAddress().getHostAddress();
        return clientIp;
    }
}

1、channelActive是连接成功后的回调

2、channelRead可以读取经过处理的消息了

3、exceptionCaught发生异常时处理

  • 启动Netty

@Slf4j
@SpringBootApplication
public class NettyDemoApplication extends SpringBootServletInitializer implements CommandLineRunner {

    @Autowired
    private NettyServer nettyServer;

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

    @Override
    public void run(String... args) throws Exception {
        //开启Netty服务
        ThreadUtil.newExecutor().submit(nettyServer);
        log.info("======服务端服务已经启动========");

    }
}

1、在主启动类中注入NettyServer因为我们的NettyServer已经注册成Component里可以归Spring管理

2、用线程池启动NettyServer

SpringBoot结合Netty实现网络编程

可以看到Springboot启动了在8080端口,而Netty启动在13001端口,代表我们的Netty服务器模式也启动了。

接下来可以测试一下。

SpringBoot结合Netty实现网络编程

我们用TCP客户端进行连接13001端口,可以看到可以连接成功,服务端会输出

2021-09-11 14:40:22.615  INFO 16256 --- [ntLoopGroup-3-1] com.pp.coder.netty.NettyServerHandler    : 有客户端进行连接:127.0.0.1
/127.0.0.1:28000

SpringBoot结合Netty实现网络编程

服务器成功收到了消息,我们的初次使用netty就成功了,后面还有高阶的使用,比如一些常见的处理方法以及断线重连和心跳包的处理,有时间分享给大家。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值