客户端(springmvc)调用netty构建的nio服务端,获得响应后返回页面(同步响应)

摘要:
稍后,我们将考虑通过netty制作一个非常简单的RPC框架。今天,我们将尝试通过正常的调用逻辑调用netty构建的nio服务器,并同步获取返回信息。

后面考虑通过netty做一个真正意义的简约版RPC框架,今天先尝试通过正常调用逻辑调用netty构建的nio服务端并同步获得返回信息。为后面做铺垫

服务端实现

我们先完成服务端的逻辑,逻辑很简单,把客户端请求的内容加上服务器时间戳一并返回

public void run() throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,4096)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new LineBasedFrameDecoder(Integer.MAX_VALUE));
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new Handler());
                        }
                    });
            System.out.println("服务启动"+port);
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
   @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String data = (String) msg;
        System.out.println("服务端接收数据:" + data);
        data = data + " now:"+System.currentTimeMillis();
        ctx.writeAndFlush(Unpooled.copiedBuffer(data, CharsetUtil.UTF_8));
    }

服务端用了LineBasedFrameDecoder,以防止半包读写问题,客户端需要进行配合

客户端实现

这个案例客户端实现有两个难点:

1、客户端方法如何能请求到SimpleChannelInboundHandler,即把需要发送的消息传到handler中

2、如何能等待服务端响应同步返回

第一个问题其实是如何把客户端输入的参数传入handler,所以我们需要把参数以构造函数传参的方式以此传入ChannelInitializer、SimpleChannelInboundHandler,这样handler就可以拿到客户端输入的数据了

下面的Controller里需要提前把channel准备好,如果RPC框架需要考虑通道与服务的关系

@RestController
public class Controller {
    static String ip = "127.0.0.1";
    static int port = 9876;
    static Bootstrap bootstrap;
    static NioEventLoopGroup worker;
    static {
        bootstrap = new Bootstrap();
        worker = new NioEventLoopGroup();
        bootstrap.group(worker);
        bootstrap.channel(NioSocketChannel .class)
        .option(ChannelOption.TCP_NODELAY, true)
        .remoteAddress(new InetSocketAddress(ip, port));
    }


    @GetMapping("/nio/netty/server")
    public String test(String param){
        CustomerChannelInitializer customerChannelInitializer = new CustomerChannelInitializer(param);
        bootstrap.handler(customerChannelInitializer);
        ChannelFuture channelFuture= null;
        String msg = "";
        try {
            channelFuture = bootstrap.connect().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return customerChannelInitializer.getResponse();
    }
}
public class CustomerChannelInitializer extends ChannelInitializer<SocketChannel> {
    private String response;
    private CountDownLatch countDownLatch;
    private String param;

    private ClientChannelHandlerAdapter clientChannelHandlerAdapter;

    public CustomerChannelInitializer(String param) {
        this.param = param;
    }
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        countDownLatch = new CountDownLatch(1);
        clientChannelHandlerAdapter = new ClientChannelHandlerAdapter(param, this);
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(clientChannelHandlerAdapter);
    }
    public String getResponse() {
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return response;
    }

    public void setResponse(String response) {
        this.response = response;
        countDownLatch.countDown();
    }
}
public class ClientChannelHandlerAdapter extends SimpleChannelInboundHandler<ByteBuf> {
    private String param;
    private CustomerChannelInitializer customerChannelInitializer;

    public ClientChannelHandlerAdapter(String param, CustomerChannelInitializer customerChannelInitializer) {
        this.param = param;
        this.customerChannelInitializer = customerChannelInitializer;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.println("客户端收到返回数据:" + msg.toString(CharsetUtil.UTF_8));
        customerChannelInitializer.setResponse(msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端准备发送数据");
        ctx.writeAndFlush(Unpooled.copiedBuffer(param + System.getProperty("line.separator"), CharsetUtil.UTF_8));
        System.out.println("客户端发送数据完成");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("发生异常");
        cause.printStackTrace();
        ctx.close();
    }
}

我们来看第二个问题,由于netty是异步的,所以无法等待到服务端响应后调用客户端的channelRead0方法,controller就已经返回了,导致了网页显示的返回结果一直是空

主线程通过CountDownLatch来锁住没有返回结果的线程,直到工作线程获得结果并解锁

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        countDownLatch = new CountDownLatch(1);
    ……

public String getResponse() { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } return response; } public void setResponse(String response) { this.response = response; countDownLatch.countDown(); }

免责声明:文章转载自《客户端(springmvc)调用netty构建的nio服务端,获得响应后返回页面(同步响应)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Mysql----mysql启动服务时提示"服务名无效"Navicat 11.2最新功能,你都get了吗?下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

心有 netty 一点通!

一、标准的netty线程模型 双池合璧: 1、连接线程池: 连接线程池专门负责监听客户端连接请求,并完成连接的建立(包括诸如握手、安全认证等过程)。 连接的建立本身是一个极其复杂、损耗性能的过程,此处使用线程池,能够极大的增加处理客户端连接的能力。 2、I/O线程池: 连接线程池会将成功建立的连接注册到后端I/O线程池,由I/O线程池负责对相应连接的网络...

Netty中的这些知识点,你需要知道!

一、Channel Channel是一个接口,而且是一个很大的接口,我们称之为“大而全”,囊括了server端及client端接口所需要的接口。 Channel是一个门面,封装了包括网络I/O及相关的所有操作。 Channel聚合了包括网络读写、链路管理、网络连接信息、获取EventLoop、Pipeline等相关功能类;统一分配,调度实现相应场景的功能。...

unity 在移动平台中,文件操作路径详解

今天,这篇文章其实是个老生常谈的问题咯,在网上类似的文章也比比皆是,在此我只是做个详细总结方便大家能够更好、更快的掌握,当然,如有不足的地方 欢迎指正!!!相信大家在开发过程中,难免会保存一些文件在客户端进行本地化操作。如:配置文件,状态文件,Assetbundle文件等等...最近总有人问我:1.保存了一个xml在客户端,能读取里面的数据,可是不能修改,...

CountDownLatch实例的await()方法

转自:http://blog.sina.com.cn/s/blog_4bed7e340101atnf.html 两年前写的程序,出了一个当时觉得莫名其妙的bug,就是线程偶尔会死掉,当时也看不出有什么问题所以当时的对策是起了一个监控线程,发现线程死掉就重启一个今天回头再去看这段代码,发现确实有漏洞代码如下public void startOneBusine...

JavaNIO学习一

文章来源:http://cucaracha.iteye.com/blog/2041847 对文件来说,可能最常用的操作就是创建、读取和写出。NIO.2 提供了丰富的方法来完成这些任务。本文从简单的小文件操作开始,最后以缓冲和非缓冲流的操作结束。 流分为输入流和输出流(可以输出到任何地方,比如硬盘或内存)。流支持不同类型的数据,比如字符串、字节、原始数据类型...

测试开发进阶——spring boot——MVC——get访问——使用@RequestParam获取参数(参数个数一致)

控制器: package com.awaimai.web; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframewo...