四、Netty概述

164 阅读6分钟

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();
}