后面考虑通过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服务端,获得响应后返回页面(同步响应)的更多相关文章

  1. Netty学习4—NIO服务端报错:远程主机强迫关闭了一个现有的连接

    1 发现问题 NIO编程中服务端会出现报错 Exception in thread "main" java.io.IOException: 远程主机强迫关闭了一个现有的连接. at ...

  2. 基于NIO的同步非阻塞编程完整案例,客户端发送请求,服务端获取数据并返回给客户端数据,客户端获取返回数据

    这块还是挺复杂的,挺难理解,但是多练几遍,多看看研究研究其实也就那样,就是一个Selector轮询的过程,这里想要双向通信,客户端和服务端都需要一个Selector,并一直轮询, 直接贴代码: Ser ...

  3. Netty源码解析---服务端启动

    Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ...

  4. NIO服务端主要创建过程

    NIO服务端主要创建过程:   步骤一:打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的副管道,示例代码如下:      ServerSocketChannel ...

  5. 如何通过JavaScript构建Asp.net服务端控件

    摘要 虽然ASP.NET的服务器控件一直被大家所诟病,但是用户控件(ACSX)在某些场景下还是非常有用的. 在一些极特珠的情况下,我们会使用JavaScript动态的构建页面中的控件,但假设遇到了我要 ...

  6. 一次http请求,谁会先断开TCP连接?什么情况下客户端先断,什么情况下服务端先断?

    我们有2台内部http服务(nginx): 201:这台服务器部署的服务是account.api.91160.com,这个服务是供前端页面调用: 202:这台服务器部署的服务是hdbs.api.911 ...

  7. Query通过Ajax向PHP服务端发送请求并返回JSON数据

    Query通过Ajax向PHP服务端发送请求并返回JSON数据 服务端PHP读取MYSQL数据,并转换成JSON数据,传递给前端Javascript,并操作JSON数据.本文将通过实例演示了jQuer ...

  8. Netty(6)源码-服务端与客户端创建

    原生的NIO类图使用有诸多不便,Netty向用户屏蔽了细节,在与用户交界处做了封装. 一.服务端创建时序图 步骤一:创建ServerBootstrap实例 ServerBootstrap是Netty服 ...

  9. NIO服务端和客户端通信demo

    代码转自 https://www.jianshu.com/p/a9d030fec081 服务端: package nio; import java.io.IOException; import jav ...

随机推荐

  1. bzoj 4974: [Lydsy八月月赛]字符串大师

    4974: [Lydsy八月月赛]字符串大师 Time Limit: 1 Sec  Memory Limit: 256 MBSubmit: 371  Solved: 190[Submit][Statu ...

  2. java整理(一)

    1.方法重载:方法名称相同,参数的类型或个数不同.但是返回值类型不同,不是方法重载. 2.引用数据类型:数组,类,接口.内存地址分为两类,栈内存和对内存.栈内存保存的是对内存的地址,简单理解就是保存了 ...

  3. 设置Input标签Date默认值为当前时间

    需求:想设置Imput标签Date默认值为当前时间,通过JavaScript实现. <html> ...... <body> <input type="date ...

  4. 关于map 及 map 骚操作

    关于map这个东西   很冷门..................   但是,这个博客带你稍微了解一下map:   map用法:一般当作一个下表无穷大的数组   关于它的骚操作:map的鬼畜用法,可以 ...

  5. BZOJ 2973 石头游戏 矩乘加速递推

    FFFFFFF,看了一上午才看懂,又调了一中午.....我终于明白为何自己如此菜了qwq 这个题加速的思路是:因为每个序列的长度小于6,他们的lcm是60,所以六十次以后就会回到原来的序列. 加速的就 ...

  6. GUI的最终选择 Tkinter(四):Entry、Listbox、Scrollbar和Scale组件

    Entry组件 Entry组件就是平时所说的输入框.输入框是程序员用到的最多的一个程序,例如在输入账号和密码的时候需要提供两个输入框,用于接收密码的输入框还会有星号将实际输入的内容隐藏起来. Tkin ...

  7. C# 用正则表达式判断字符串是否为纯数字

    Regex regex = new System.Text.RegularExpressions.Regex("^(-?[0-9]*[.]*[0-9]{0,3})$"); stri ...

  8. Kotlin容器

    1. 容器 可变/不可变 List<out T> 只读list; MutableList<T>; Set<out T>/MutableSet<T> Ma ...

  9. 将MySQL转化为mysqli

    <?php/** * Created by PhpStorm. * User: 大神 * Date: 2017/7/24 * Time: 11:29 */ header('content-typ ...

  10. LR C语言语句复习,几个简单代码

    嵌套循环 Action() { int i,j; ;i<=;i++) { ) beark; else lr_output_message("i=%d",i); ;j<= ...