使用Netty实现客户端和服务端之间的双向通信

2 篇文章 1 订阅
1 篇文章 0 订阅

欢迎阅读本篇文章

提示:本文只是提供部分核心代码,源码详见代码示例

使用Netty实现客户端和服务端之间的双向通信


前言

在上个月的开发计划中,有一个系统控制喇叭播放的功能。当时就想到了使用netty进行通信操作。于是在调研途中,发现网上写的都是简单案例,不适用于当前的复杂通信模式。比如:超时断线,断线重连,通信监听,错误记录存储,以及断线和上线后的钉钉通知等等。所以自己从头到尾重新写了一个完整项目,供大家参考。


提示:以下是本篇文章正文内容,下面案例可供参考

一、服务端

在netty的通讯服务中,需先启动服务端,提供自身的端口和IP供客户端连接。

代码如下(示例):

@Slf4j
public class NettyServer {

    public Result bind(int port) throws Exception {
        try {
            EventLoopGroup bossGroup = new NioEventLoopGroup(); //bossGroup就是parentGroup,是负责处理TCP/IP连接的
            EventLoopGroup workerGroup = new NioEventLoopGroup(); //workerGroup就是childGroup,是负责处理Channel(通道)的I/O事件

            ServerBootstrap sb = new ServerBootstrap();
            sb.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128) //初始化服务端可连接队列,指定了队列的大小128
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //保持长连接
                    .childHandler(new ChannelInitializer<SocketChannel>() {  // 绑定客户端连接时候触发操作
                        @Override
                        protected void initChannel(SocketChannel sh) throws Exception {
                            sh.pipeline()
                                    .addLast(new RpcDecoder(RpcRequest.class)) //解码request
                                    .addLast(new RpcEncoder(RpcResponse.class)) //编码response
                                      //readerIdleTime:读超时时间;  writerIdleTime:写超时时间; allIdleTime:所有类型超时时间;
                                      //.addLast(new IdleStateHandler(10, 10, 10, TimeUnit.SECONDS))
                                    .addLast(new ServerHandler()); //使用ServerHandler类来处理接收到的消息
                        }
                    });
            //绑定监听端口,调用sync同步阻塞方法等待绑定操作完成,完成后返回ChannelFuture类似于JDK中Future
            ChannelFuture future = sb.bind(port).sync();
            log.info("服务端绑定端口:"+port+"成功!");

            if (future.isSuccess()) {
                log.info("服务端启动成功");
                List<ChannelFuture> channelFuture = ServerHandler.getChannelFuture();
                channelFuture.add(future);
                List<EventLoopGroup> eventLoopGroup = ServerHandler.getEventLoopGroup();
                eventLoopGroup.add(workerGroup);
                eventLoopGroup.add(bossGroup);
            } else {
                log.error("服务端启动失败");
                future.cause().printStackTrace();
                bossGroup.shutdownGracefully(); //关闭线程组
                workerGroup.shutdownGracefully();
                return Result.ERROR_INFO("Netty服务端启动失败失败!");
            }

            //成功绑定到端口之后,给channel增加一个 管道关闭的监听器并同步阻塞,直到channel关闭,线程才会往下执行,结束进程。
            future.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }

        return Result.ERROR_INFO("Netty服务端发送消息成功!");
    }


   /* public static void main(String[] args) throws Exception {
        //启动server服务
        new NettyServer().bind(9999);
    }*/
}

在上面的代码示例中,最重要的就是 ServerHandler 方法。继承ChannelInboundHandlerAdapter。实现 channelActive(有客户端连接服务器会触发此函数) channelInactive(有客户端终止连接服务器会触发此函数)等一些方法。一些的读操作时捕获到异常就可以调用 exceptionCaught()。更具体详细内容可以自行查阅。但是我的代码示例中已经基本包含了所需用到的方法,大家可以下载后可以看一看。

特别提醒:
在上面的服务端代码中,使用了 ChannelFuture future = sb.bind(port).sync()【绑定监听端口,调用sync同步阻塞方法等待绑定操作完成,完成后返回ChannelFuture类似于JDK中Future】。所以项目如果使用了启动就开启服务端,那么一定要加线程池进行操作,要不然项目就会卡在不动无法完成启动。

 log.info("开始启动预加载服务");
        if (contextRefreshedEvent.getApplicationContext().getParent() == null) {
            try {
                log.info("项目启动,开启Netty服务!");
                new Thread(() -> {
                    try {
                        new NettyServer().bind(configProperties.getNettyServerPort());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
            } catch (Exception e) {
                log.error("项目启动时,Netty服务开启失败!");
                e.printStackTrace();
            }
        }

二、客户端

启动好服务端以后,开始启动客户端

代码如下(示例):

@Slf4j
public class NettyClient {
    private final String host;
    private final int port;

    //连接服务端的端口号地址和端口号
    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    private volatile Channel clientChannel;
    Bootstrap bootstrap;
    EventLoopGroup group;


    public void start() {
        group = new NioEventLoopGroup();
        //创建客户端启动对象,注意客户端使用的不是ServerBootStrap而是BootStrap
        bootstrap = new Bootstrap();
        ChannelFuture future;  //ChannelFuture的作用是用来保存Channel异步操作的结果

        try {
            bootstrap.group(group).channel(NioSocketChannel.class)  // 使用NioSocketChannel来作为连接用的channel类
                    .handler(new ChannelInitializer<SocketChannel>() { // 绑定连接初始化器
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("正在连接服务端中...");
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new RpcEncoder(RpcRequest.class)); //编码request
                            pipeline.addLast(new RpcDecoder(RpcResponse.class)); //解码response

                            // 3s 内如果没有向服务器写数据,会触发一个 IdleState#WRITER_IDLE 事件  用来判断是不是 读空闲时间过长,或 写空闲时间过长
                            //readerIdleTime:读超时时间;  writerIdleTime:写超时时间; allIdleTime:所有类型超时时间;
                            //pipeline.addLast(new IdleStateHandler(10, 10, 10, TimeUnit.SECONDS));
                            pipeline.addLast(new ClientHandler(new NettyClient(host, port)));

                        }
                    });
            //发起异步连接请求,绑定连接端口和host信息
            future = bootstrap.connect(host, port).sync();

            //添加重连测试
            ClientHandler.getBootstrap().put("nettyServer",bootstrap);

            future.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture arg0) throws Exception {
                    if (future.isSuccess()) {
                        System.out.println("连接服务器成功");
                        List<ChannelFuture> channelFuture = ClientHandler.getChannelFuture();
                        channelFuture.add(future);
                        List<EventLoopGroup> eventLoopGroup = ClientHandler.getEventLoopGroup();
                        eventLoopGroup.add(group);

                    } else {
                        System.out.println("连接服务器失败");
                        future.cause().printStackTrace();
                        group.shutdownGracefully(); //关闭线程组
                        throw new BizException(500,"连接Netty服务器失败",null);
                    }
                }
            });
            future.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
            log.error("客户端启动失败!服务端不在线!", e);
            throw new BizException(500,"客户端启动失败!服务端不在线!"+e,null);
        }
    }

特别提醒:
若有时候真的忘记了先启动服务端怎么办呢,问题不大!在代码中我已经做了重连机制。使用线程池在规定的时间间隔中,循环进行尝试重连。

new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                try {
                    new NettyClient(monitorIp, monitorPort).start();  //启动netty
                }catch (BizException e){
                    if (e.getErrorMsg().contains("服务端不在线")){
                        log.error("--------------------------error----------------------->"+e.getErrorMsg());
                        //连接异常  服务端没有开启 进行重新连接
                        DingDingSendUtil.postSend("客户端自启动失败!Netty服务端不在线!请检查服务!");
                        new ClientHandler(new NettyClient(monitorIp, monitorPort)).initScheduledExecutor(null);
                    }
                    e.printStackTrace();
                    throw new BizException(500,"Netty客户端【KHD_ONE】,启动异常!"+e.getMessage(),null);
                }
            }
        },"NettyServer").start();

Netty通信的服务就是这么多内容,核心都在 ClientHandler 和 ServerHandler 中,剩下的是一些通过TCP与喇叭进行通信的内容了。有兴趣的也可以看看如何进行操作。这个功能模块基本上用了一个月时间完成,使用到了异步通信,多线程与线程池,TCP通信,定时任务等等。毕竟花了很多心血,已经部署上线可靠运行。有兴趣的朋友可以加QQ:1051266367 找博主要源码。

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
好的,这是一个很好的问题。使用Spring Boot和Netty实现双向通信的过程如下: 1. 首先,在pom.xml中添加以下依赖项: ``` <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.25.Final</version> </dependency> ``` 2. 创建Netty服务器 ``` @Component public class NettyServer { private final EventLoopGroup bossGroup = new NioEventLoopGroup(); private final EventLoopGroup workerGroup = new NioEventLoopGroup(); private Channel channel; @Autowired private NettyServerHandler nettyServerHandler; public void start() throws Exception { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(nettyServerHandler); } }); ChannelFuture f = bootstrap.bind(8888).sync(); if (f.isSuccess()) { System.out.println("Netty server start success"); } channel = f.channel().closeFuture().sync().channel(); } public void stop() throws Exception { channel.close(); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } ``` 在这个示例中,我们创建了一个名为NettyServer的类。这个类使用Netty的ServerBootstrap类来创建服务器。在服务器启动时,我们需要指定使用的IO模式和端口号。此外,我们还需要指定NettyServerHandler类,这个类用于处理客户端发来的消息。 3. 创建NettyServerHandler类 ``` @Component @ChannelHandler.Sharable public class NettyServerHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Server received:" + msg); // 回复客户端 ctx.writeAndFlush("Server received your message: " + msg + "\n"); // 其他业务逻辑 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("Server exceptionCaught"); cause.printStackTrace(); ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("Server channelActive"); super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("Server channelInactive"); super.channelInactive(ctx); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { System.out.println("Server channelReadComplete"); super.channelReadComplete(ctx); } } ``` 在这个示例中,我们创建了一个名为NettyServerHandler的类。这个类用于处理客户端发来的消息。我们需要实现channelRead0方法,这个方法会在客户端发送消息时被调用。在这个方法中,我们可以处理客户端发送的消息,并且回复消息给客户端。除此之外,我们还需要实现其他方法,如exceptionCaught、channelActive、channelInactive和channelReadComplete。 4. 创建Netty客户端 ``` @Component public class NettyClient { private Channel channel; private final EventLoopGroup group = new NioEventLoopGroup(); @Autowired private NettyClientHandler nettyClientHandler; public void start() throws Exception { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(nettyClientHandler); } }); ChannelFuture f = bootstrap.connect("localhost", 8888).sync(); if (f.isSuccess()) { System.out.println("Netty client start success"); } channel = f.channel().closeFuture().sync().channel(); } public void stop() throws Exception { channel.close(); group.shutdownGracefully(); } } ``` 在这个示例中,我们创建了一个名为NettyClient的类。这个类使用Netty的Bootstrap类来创建客户端。在客户端启动时,我们需要指定使用的IO模式和服务器的IP地址和端口号。此外,我们还需要指定NettyClientHandler类,这个类用于处理服务器发来的消息。 5. 创建NettyClientHandler类 ``` @Component @ChannelHandler.Sharable public class NettyClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Client received:" + msg); // 其他业务逻辑 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("Client exceptionCaught"); cause.printStackTrace(); ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("Client channelActive"); super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("Client channelInactive"); super.channelInactive(ctx); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { System.out.println("Client channelReadComplete"); super.channelReadComplete(ctx); } } ``` 在这个示例中,我们创建了一个名为NettyClientHandler的类。这个类用于处理服务器发来的消息。我们需要实现channelRead0方法,这个方法会在服务器发送消息时被调用。在这个方法中,我们可以处理服务器发送的消息。除此之外,我们还需要实现其他方法,如exceptionCaught、channelActive、channelInactive和channelReadComplete。 6. 在Spring Boot中启动Netty服务器和客户端 最后,在Spring Boot应用程序的启动类中,我们需要启动Netty服务器和客户端。我们可以使用@PostConstruct注释来启动Netty服务器和客户端。 ``` @SpringBootApplication public class Application { @Autowired private NettyServer nettyServer; @Autowired private NettyClient nettyClient; @PostConstruct public void start() throws Exception { new Thread(() -> { try { nettyServer.start(); } catch (Exception e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { nettyClient.start(); } catch (Exception e) { e.printStackTrace(); } }).start(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 好了,以上就是使用Spring Boot和Netty实现双向通信的全部过程。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咕噜咕噜虎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值