NIO和Netty全文
RPC
- Remote Procedure Call,远程过程调用,计算机通信协议,允许运行于一台计算机的程序,调用另一台计算机的子程序,无需额外为这个交互作用编程
两个或多个应用程序都分布在不同服务器上,之间的调用像是本地调用
- 常用RPC框架,
阿里Dubbo、谷歌gRPC、GO语言的rpcx、Apache的thtift、Spring Cloud
- 调用流程,将
2-7
封装
- 客户端(服务消费者)请求服务
clientStub
接收到调用,将方法、参数进行封装- 编码,并发送服务端(服务生产方)
serverStub
接受,并解码- 调用API,并响应,生成结果
serverStub
编码,并发送给客户端clientStub
接受,并解码- 最终调用者得到结果
dubbo RPC
- 接口,定义抽象方法,用于消费者和提供者之间的约定
- 提供者,该类监听消费者的请求,按照约定返回数据
- 消费者,透明地调用自己不存在的方法,内部使用Netty请求提供者发挥数据
公共接口
public interface IHelloService {
String hello(String msg);
}
NettyServer
public class NettyServer {
public static void startServer(String hostname, int port) {
startServer0(hostname, port);
}
//初始化和启动
private static void startServer0(String hostname, int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
new ServerBootstrap().group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.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(new NettyServerHandler());
}
}).bind(hostname, port).sync().channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
HelloServiceImpl
- 服务端的提供服务接口
public class HelloServiceImpl implements IHelloService {
//count不变,每次调用,都会生成一个HelloServiceImpl实例
private int count = 0;
//当消费方调用,返回结果
@Override
public String hello(String msg) {
System.out.println("收到: " + msg);
//根据msg返回不同结果
if (msg != null) {
return "已经收到消息 [ " + msg + " ] 第 " + count++ + " 次...";
} else {
return "收到消息为空...";
}
}
}
NettyServerHandler
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//获取客户端发送的消息,并调用服务
System.out.println("netty server receive: " + msg);
//客户端再调用服务器服务时,需要定义一个协议
//每次发消息,都必须以某个字符串开头
if (msg.toString().startsWith("HelloService#hello#")) {
//截取真正的内容
String res = new HelloServiceImpl().hello(
msg.toString().substring(
msg.toString().lastIndexOf("#") + 1));
//回写
ctx.writeAndFlush(res);
} else {
System.out.println("prefix error");
ctx.writeAndFlush("prefix error");
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
NettyClient
TCP_NODELAY
允许小包发送- 使用代理调用实现异步调用远程服务
public class NettyClient {
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
private static NettyClientHandler clientHandler;
//使用代理模式,获取代理对象
public Object getBean(final Class<?> serviceClass, final String prefix) {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] {serviceClass}, //接口数组
((proxy, method, args) -> {
if (clientHandler == null) {
initClient();
}
//设置发给服务器端的信息 协议头 + 第一个参数
clientHandler.setPara(prefix + args[0]);
//get() 获取 res结果
return executorService.submit(clientHandler).get();
}));
}
//初始化客户端
private static void initClient() {
clientHandler = new NettyClientHandler();
EventLoopGroup group = new NioEventLoopGroup();
try {
new Bootstrap().group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(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(clientHandler);
}
}).connect("localhost", 9999).sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//不能关闭,不能阻塞,因为这是一个函数调用,执行完之后,才能执行下面的代码
//group.shutdownGracefully();
}
}
}
NettyClientHandler
- 实现
Callable
,直到有返回值时,才继续执行,否则wait()
等待 channelRead
直到读到数据,唤醒线程
public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {
private ChannelHandlerContext context;
private String res;
private String para; //客户端调用方式时的参数
//channelActive -> setPara -> call -> channelRead -> call
public void setPara(String para) {
this.para = para;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
//在其他方法,需要使用上下文
context = ctx;
}
//第一个被调用
//synchronized 同步执行
@Override
public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("channelRead");
res = msg.toString();
notify(); //拿到结果后,唤醒等待的线程
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
//被代理对象调用,发送数据给服务器,并且等待被唤醒,因此要同步执行
@Override
public synchronized Object call() throws Exception {
System.out.println("call");
context.writeAndFlush(para);
//唤醒后,拿到结果
wait();
System.out.println("call");
return res; //服务方返回的结果
}
}
Bootstrap
- Client
public class ClientBootstrap {
//协议头
public static final String PROVIDERNAME = "HelloService#hello#";
public static void main(String[] args) throws ExecutionException, InterruptedException {
NettyClient customer = new NettyClient();
IHelloService server = (IHelloService) customer.getBean(IHelloService.class, PROVIDERNAME);
while (true) {
Thread.sleep(5000);
//实际调用的是代理对象的方法
String res = server.hello("你好 dubbo~");
System.out.println("res: " + res);
}
}
}
- Server
public class ServerBootstrap {
public static void main(String[] args) {
NettyServer.startServer("localhost", 9999);
}
}