前提摘要
- Java的BIO与线程池网络传输性能太低,我们需要升级为基于NIO 的异步网络编程框架,即本文的主角Netty(网状的)
NIO和BIO的区别
- IO 网络通讯方式:
首先Java中的IO都是依赖操作系统内核进行的,我们程序中的IO读写其实调用的是操作系统内核中的read&write两大系统调用。 那内核是如何进行IO交互的呢?
-
网卡收到经过网线传来的网络数据,并将网络数据写到内存中。
-
当网卡把数据写入到内存后,网卡向cpu发出一个中断信号,操作系统便能得知有新数据到来,再通过网卡中断程序去处理数据。
-
将内存中的网络数据写入到对应socket的接收缓冲区中。
-
当接收缓冲区的数据写好之后,应用程序开始进行数据处理
-
BIO全称是Blocking IO,是同步阻塞IO模型。NIO叫Non-Blocking IO 是同步非阻塞的IO模型 阻塞指的是遇到同步等待后,一直在原地等待同步方法处理完成。非阻塞指的是遇到同步等待,不在原地等待,先去做其他的操作,隔段时间再来观察同步方法是否完成。显然NIO的效率高多啦!
-
Netty的通信方式
创建服务端启动类
public class NettyRPCServer implements RPCServer {
private ServiceProvider serviceProvider;
@Override
public void start(int port) {
// netty 服务线程组boss负责建立连接, work负责具体的请求
//创建两个线程组 boosGroup、workerGroup
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
System.out.printf("Netty服务端启动了...");
try {
// 启动netty服务器
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 初始化
// 配置两个线程组boosGroup和workerGroup
serverBootstrap.group(bossGroup,workGroup)
//设置服务端通道实现类型
.channel(NioServerSocketChannel.class)
//使用匿名内部类的形式初始化通道对象
.childHandler(new NettyServerInitializer(serviceProvider));
// 绑定端口号,启动服务端,同步阻塞
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
// 对关闭通道进行监听,死循环监听
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
@Override
public void stop() {
}
}
创建服务端处理器
public class NettyRPCServerHandler extends SimpleChannelInboundHandler<RPCRequest> {
private ServiceProvider serviceProvider;
@Override
protected void channelRead0(ChannelHandlerContext ctx, RPCRequest msg) throws Exception {
//获取客户端发送过来的消息
//System.out.println(msg);
RPCResponse response = getResponse(msg);
ctx.writeAndFlush(response);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//发生异常,关闭通道
cause.printStackTrace();
ctx.close();
}
RPCResponse getResponse(RPCRequest request) {
// 得到服务名
String interfaceName = request.getInterfaceName();
// 得到服务端相应服务实现类
Object service = serviceProvider.getService(interfaceName);
// 反射调用方法
Method method = null;
try {
method = service.getClass().getMethod(request.getMethodName(), request.getParamsTypes());
Object invoke = method.invoke(service, request.getParams());
return RPCResponse.success(invoke);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
System.out.println("方法执行错误");
return RPCResponse.fail();
}
}
}
创建客户端启动类
public class NettyRPCClient implements RPCClient {
private static final Bootstrap bootstrap;
private static final EventLoopGroup eventLoopGroup;
private String host;
private int port;
public NettyRPCClient(String host, int port) {
this.host = host;
this.port = port;
}
// netty客户端初始化,重复使用
static {
eventLoopGroup = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new NettyClientInitializer());
}
/**
* 这里需要操作一下,因为netty的传输都是异步的,你发送request,会立刻返回一个值, 而不是想要的相应的response
*/
@Override
public RPCResponse sendRequest(RPCRequest request) {
try {
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
Channel channel = channelFuture.channel();
// 发送数据
channel.writeAndFlush(request);
channel.closeFuture().sync();
// 阻塞的获得结果,通过给channel设计别名,获取特定名字下的channel中的内容(这个在hanlder中设置)
// AttributeKey是,线程隔离的,不会由线程安全问题。
// 实际上不应通过阻塞,可通过回调函数
AttributeKey<RPCResponse> key = AttributeKey.valueOf("RPCResponse");
RPCResponse response = channel.attr(key).get();
System.out.println(response);
return response;
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
创建客户端处理器
public class NettyClientHandler extends SimpleChannelInboundHandler<RPCResponse> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RPCResponse msg) throws Exception {
// 接收到response, 给channel设计别名,让sendRequest里读取response
AttributeKey<RPCResponse> key = AttributeKey.valueOf("RPCResponse");
ctx.channel().attr(key).set(msg);
ctx.channel().close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}