Netty概述
对于一些大型项目,往往部署时,需要将进程放到不同的服务器上运行。进程之间的调用不像在一台服务器上直接调用方法来的方便。最开始的方式是使用http协议。但是http协议中包含了很多信息是进程间调用用不到的,例如请求头和响应头中的很多字段。我们想要的是很纯粹的进程间调用的协议,例如调用的方法名、入参和出参即可。
Netty框架是一个基于Java NIO的高性能RPC框架,封装了Java NIO复杂的使用方式,提供了简单的API,实现了进程间的通讯犹如进程间调用方法一般简单。Netty是一个框架,可以通过这个框架自定义出满足自身业务需求的RPC框架。例如阿里巴巴的Dubbo就是基于Netty实现的。
基于Netty的API的网络通讯简单实现
服务端实现:
public void serverMode() throws Exception {
// 多路复用器组
// 这个组中只有一个多路复用器
NioEventLoopGroup thread = new NioEventLoopGroup(1);
// 初始化服务端
NioServerSocketChannel server = new NioServerSocketChannel();
// 将服务端放到多路复用器组中
thread.register(server);
// 获取服务端的ChannelPipeline对象
// ChannelPipeline对象中存放accept、read事件触发时要执行的函数
// write事件是不需要触发的,只要内核中的write_queue有空间就可以随时写
ChannelPipeline p = server.pipeline();
// 在ChannelPipeline对象中存放accept事件触发时调用的函数
// 调用的函数是MyAcceptHandler对象中的channelRead方法
// 在MyAcceptHandler对象中传入了多路复用器和ChannelInit对象
// 因为accept事件触发后,说明有客户端建立连接,服务端会新创建一个SocketChannel对象,需要放到多路复用器组中
// 并且SocketChannel对象可能会有read事件触发
// 所以需要将事件触发后调用的函数放到SocketChannel对象的pipeline中,即ChannelInit对象的channelRead方法
p.addLast(new MyAcceptHandler(thread, new ChannelInit()));
// 服务端绑定IP和端口号
ChannelFuture bind = server.bind(new InetSocketAddress("192.168.150.1", 9090));
// bind.sync()是在等待客户端建立连接,没有建立连接,线程会一直堵塞在这个方法中
// 后续的channel().closeFuture().sync()是等待连接断开,如果没有断开,线程也会一直堵塞
bind.sync().channel().closeFuture().sync();
System.out.println("server close....");
}
class MyAcceptHandler extends ChannelInboundHandlerAdapter {
// 多路复用器组
private final EventLoopGroup selector;
// 客户端建立连接后,服务端会新创建一个SocketChannel对象
// 并且SocketChannel对象可能会有read事件触发,事件触发后调用的函数是handler的channelRead方法
private final ChannelHandler handler;
public MyAcceptHandler(EventLoopGroup thread, ChannelHandler myInHandler) {
this.selector = thread;
this.handler = myInHandler;
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("server registerd...");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 建立连接后,服务端创建的SocketChannel对象
SocketChannel client = (SocketChannel) msg;
ChannelPipeline p = client.pipeline();
// 将read事件触发后执行的函数放到pipeline中
p.addLast(handler);
// 将SocketChannel对象放到多路复用器组中
selector.register(client);
}
}
// Netty框架规定,每个socket事件触发后调用的函数都必须是自己的
// 而在整个代码中,ChannelInit对象只会创建出一个,所有的socket都在用这一个对象,不合理
// 所以需要加上@Sharable注解
// ChannelInit对象的作用不是进行业务逻辑处理,而是将accept事件触发后创建的SocketChannel对象
// 在以后read事件触发后要调用的函数放到SocketChannel对象的pipeline中
// 这样写的好处是可以保证每个socket都有自己的事件触发的函数去调用
@ChannelHandler.Sharable
class ChannelInit extends ChannelInboundHandlerAdapter {
// 此方法是将SocketChannel对象以后read事件触发后要调用的函数放到SocketChannel对象的pipeline中
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
Channel client = ctx.channel();
ChannelPipeline p = client.pipeline();
// 每有一个客户端连接了
// 就会创建一个MyInHandler对象,只属于这个客户端
p.addLast(new MyInHandler());
ctx.pipeline().remove(this);
}
// 此方法啥也不干
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
}
class MyInHandler extends ChannelInboundHandlerAdapter {
// 啥也不干
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("client registed...");
}
// 啥也不干
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client active...");
}
// 业务处理
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// read事件触发后,内核将数据放到ByteBuf中
ByteBuf buf = (ByteBuf) msg;
// 使用get方法而不是read方法从ByteBuf中读取数据
// 这是因为read方法会移动readIndex,get方法不会
// 而如果readIndex比writeIndex值要大的话
// write方法是会阻塞不执行的
// CharSequence str = buf.readCharSequence(buf.readableBytes(), CharsetUtil.UTF_8);
CharSequence str = buf.getCharSequence(0, buf.readableBytes(), CharsetUtil.UTF_8);
System.out.println(str);
ctx.writeAndFlush(buf);
}
}
客户端实现:
public void clientMode() throws Exception {
// 多路复用器组
// 这个组中只有一个多路复用器
NioEventLoopGroup thread = new NioEventLoopGroup(1);
// 初始化客户端
NioSocketChannel client = new NioSocketChannel();
// 将客户端放到多路复用器组中
thread.register(client);
// 获取客户端的ChannelPipeline对象
// ChannelPipeline对象中存放read事件触发时要执行的函数
// write事件是不需要触发的,只要内核中的write_queue有空间就可以随时写
ChannelPipeline p = client.pipeline();
// 在ChannelPipeline对象中存放read事件触发时调用的函数
// 调用的函数是MyInHandler对象中的channelRead方法
p.addLast(new MyInHandler());
// 客户端连接服务端
ChannelFuture connect = client.connect(new InetSocketAddress("192.168.150.11", 9090));
// connect.sync()是在等待建立连接完成,没有建立连接,线程会一直堵塞在这个方法中
ChannelFuture sync = connect.sync();
// 客户端发送数据
ByteBuf buf = Unpooled.copiedBuffer("hello server".getBytes());
ChannelFuture send = client.writeAndFlush(buf);
// 等待数据发送完成
send.sync();
// 等待连接断开
sync.channel().closeFuture().sync();
System.out.println("client over....");
}
基于Netty的API的网络通讯正常实现
服务端实现:
public void nettyServer() throws InterruptedException {
// 多路复用器组
// 这个组中只有一个多路复用器
NioEventLoopGroup group = new NioEventLoopGroup(1);
// ServerBootstrap对象封装了简单实现中的服务端复杂流程
ServerBootstrap bs = new ServerBootstrap();
// 传入两个多路复用器组,一个作为boss组,一个作为worker组
ChannelFuture bind = bs.group(group, group)
// 传入服务端的NioServerSocketChannel类,netty会自动创建对象
.channel(NioServerSocketChannel.class)
// childHandler会自动处理accept事件触发
// accept事件触发后,会调用ChannelInitializer对象的initChannel方法
// ChannelInitializer对象的initChannel方法其实就起到一个过渡作用
// 负责将read事件触发后要执行的函数放到pipeline中,而且ChannelInitializer必须是被@Sharable修饰的
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new MyInHandler());
}
})
// 绑定IP和端口
.bind(new InetSocketAddress("192.168.150.1", 9090));
bind.sync().channel().closeFuture().sync();
}
客户端实现:
public void nettyClient() throws InterruptedException {
// 多路复用器组
// 这个组中只有一个多路复用器
NioEventLoopGroup group = new NioEventLoopGroup(1);
Bootstrap bs = new Bootstrap();
ChannelFuture connect = bs.group(group)
// 传入服务端的NioSocketChannel类,netty会自动创建对象
.channel(NioSocketChannel.class)
// read事件触发后,会调用ChannelInitializer对象的initChannel方法
// ChannelInitializer对象的initChannel方法其实就起到一个过渡作用
// 负责将read事件触发后要执行的函数放到pipeline中,而且ChannelInitializer必须是被@Sharable修饰的
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new MyInHandler());
}
})
// 连接服务端
.connect(new InetSocketAddress("192.168.150.11", 9090));
Channel client = connect.sync().channel();
ByteBuf buf = Unpooled.copiedBuffer("hello server".getBytes());
ChannelFuture send = client.writeAndFlush(buf);
send.sync();
client.closeFuture().sync();
}