Netty架构原理

https://www.w3cschool.cn/essential_netty_in_action/essential_netty_in_action-x7mn28bx.html

netty server创建

package com.cn.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.stereotype.Component;
import com.cn.common.core.codc.RequestDecoder;
import com.cn.common.core.codc.ResponseEncoder;

/**
 * netty服务端入门
 * 
 * @author -琴兽-
 * 
 */
@Component
public class Server {

	/**
	 * 启动
	 */
	public void start() {

		// 服务类
		ServerBootstrap b = new ServerBootstrap();

		// 创建boss和worker
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();

		try {
			// 设置循环线程组事例
			b.group(bossGroup, workerGroup);

			// 设置channel工厂
			b.channel(NioServerSocketChannel.class);

			// 设置管道
			b.childHandler(new ChannelInitializer<SocketChannel>() {
				@Override
				public void initChannel(SocketChannel ch) throws Exception {
					ch.pipeline().addLast(new RequestDecoder());
					ch.pipeline().addLast(new ResponseEncoder());
					ch.pipeline().addLast(new ServerHandler());
					ch.pipeline().addLast(new ServerHandler2());
				}
			});

			b.option(ChannelOption.SO_BACKLOG, 2048);// 链接缓冲池队列大小

			// 绑定端口
			b.bind(10102).sync();

			System.out.println("start!!!");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

 

 

Server 的启动方法涉及到了一些组件的调用,例如 EventLoopGroup,Channel。这些会在后面详细讲解。

这里有个大致的印象就好:

  • 创建 EventLoopGroup。
  • 创建 ServerBootstrap。
  • 指定所使用的 NIO 传输 Channel。
  • 使用指定的端口设置套接字地址。
  • 添加一个 ServerHandler 到 Channel 的 ChannelPipeline。
  • 异步地绑定服务器;调用 sync 方法阻塞等待直到绑定完成。
  • 获取 Channel 的 CloseFuture,并且阻塞当前线程直到它完成。
  • 关闭 EventLoopGroup,释放所有的资源。

NettyServer 启动以后会监听某个端口的请求,当接受到了请求就需要处理了。在 Netty 中客户端请求服务端,被称为“入站”操作。

 

客户端代码

客户端和服务端的代码基本相似,在初始化时需要输入服务端的 IP 和 Port。

 

package com.cn.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.cn.client.swing.Swingclient;
import com.cn.common.core.codc.RequestEncoder;
import com.cn.common.core.codc.ResponseDecoder;
import com.cn.common.core.model.Request;

/**
 * netty客户端入门
 * 
 * @author -琴兽-
 * 
 */
@Component
public class Client {
	
	/**
	 * 界面
	 */
	@Autowired
	private Swingclient swingclient;

	/**
	 * 服务类
	 */
	Bootstrap bootstrap = new Bootstrap();

	/**
	 * 会话
	 */
	private Channel channel;

	/**
	 * 线程池
	 */
	private EventLoopGroup workerGroup = new NioEventLoopGroup();

	/**
	 * 初始化
	 */
	@PostConstruct
	public void init() {
		
		// 设置循环线程组事例
		bootstrap.group(workerGroup);

		// 设置channel工厂
		bootstrap.channel(NioSocketChannel.class);

		// 设置管道
		bootstrap.handler(new ChannelInitializer<SocketChannel>() {
			@Override
			public void initChannel(SocketChannel ch) throws Exception {
				ch.pipeline().addLast(new ResponseDecoder());
				ch.pipeline().addLast(new RequestEncoder());
				ch.pipeline().addLast(new ClientHandler(swingclient));
			}
		});
	}

	/**
	 * 连接
	 * 
	 * @param ip
	 * @param port
	 * @throws InterruptedException
	 */
	public void connect() throws InterruptedException {

		// 连接服务端
		ChannelFuture connect = bootstrap.connect(new InetSocketAddress("127.0.0.1", 10102));
		connect.sync();
		channel = connect.channel();
	}

	/**
	 * 关闭
	 */
	public void shutdown() {
		workerGroup.shutdownGracefully();
	}

	/**
	 * 获取会话
	 * 
	 * @return
	 */
	public Channel getChannel() {
		return channel;
	}
	
	/**
	 * 发送消息
	 * @param request
	 * @throws InterruptedException 
	 */
	public void sendRequest(Request request) throws InterruptedException{
		if(channel == null || !channel.isActive()){
			connect();
		}
		channel.writeAndFlush(request);
	}
	
}

 

客户端启动程序的顺序:

  • 创建 Bootstrap。
  • 指定 EventLoopGroup 用来监听事件。
  • 定义 Channel 的传输模式为 NIO(Non-BlockingInputOutput)。
  • 设置服务器的 InetSocketAddress。
  • 在创建 Channel 时,向 ChannelPipeline 中添加一个 EchoClientHandler 实例。
  • 连接到远程节点,阻塞等待直到连接完成。
  • 阻塞,直到 Channel 关闭。
  • 关闭线程池并且释放所有的资源。

客户端在完成以上操作以后,会与服务端建立连接从而传输数据。同样在接受到 Channel 中触发的事件时,客户端会触发对应事件的操作。

Netty 核心组件

通过上面的简单例子,发现有些 Netty 组件在服务初始化以及通讯时被用到,下面就来介绍一下这些组件的用途和关系。

①Channel

通过上面例子可以看出,当客户端和服务端连接的时候会建立一个 Channel。

这个 Channel 我们可以理解为 Socket 连接,它负责基本的 IO 操作,例如:bind,connect,read,write 等等。

简单的说,Channel 就是代表连接,实体之间的连接,程序之间的连接,文件之间的连接,设备之间的连接。同时它也是数据入站和出站的载体。

②EventLoop 和 EventLoopGroup

既然有了 Channel 连接服务,让信息之间可以流动。如果服务发出的消息称作“出站”消息,服务接受的消息称作“入站”消息。那么消息的“出站”/“入站”就会产生事件(Event)。

例如:连接已激活;数据读取;用户事件;异常事件;打开链接;关闭链接等等。

顺着这个思路往下想,有了数据,数据的流动产生事件,那么就有一个机制去监控和协调事件。

这个机制(组件)就是 EventLoop。在 Netty 中每个 Channel 都会被分配到一个 EventLoop。一个 EventLoop 可以服务于多个 Channel。

每个 EventLoop 会占用一个 Thread,同时这个 Thread 会处理 EventLoop 上面发生的所有 IO 操作和事件(Netty 4.0)。

EventLoop 与 Channel 关系

理解了 EventLoop,再来说 EventLoopGroup 就容易了,EventLoopGroup 是用来生成 EventLoop 的,还记得例子代码中第一行就 new 了 EventLoopGroup 对象。

一个 EventLoopGroup 中包含了多个 EventLoop 对象。

创建 EventLoopGroup

EventLoopGroup 要做的就是创建一个新的 Channel,并且给它分配一个 EventLoop。

EventLoopGroup,EventLoop 和 Channel 的关系

在异步传输的情况下,一个 EventLoop 是可以处理多个 Channel 中产生的事件的,它主要的工作就是事件的发现以及通知。

相对于以前一个 Channel 就占用一个 Thread 的情况。Netty 的方式就要合理多了。

客户端发送消息到服务端,EventLoop 发现以后会告诉服务端:“你去获取消息”,同时客户端进行其他的工作。

当 EventLoop 检测到服务端返回的消息,也会通知客户端:“消息返回了,你去取吧“。客户端再去获取消息。整个过程 EventLoop 就是监视器+传声筒。

③ChannelHandler,ChannelPipeline 和 ChannelHandlerContext

如果说 EventLoop 是事件的通知者,那么 ChannelHandler 就是事件的处理者。

在 ChannelHandler 中可以添加一些业务代码,例如数据转换,逻辑运算等等。

正如上面例子中展示的,Server 和 Client 分别都有一个 ChannelHandler 来处理,读取信息,网络可用,网络异常之类的信息。

并且,针对出站和入站的事件,有不同的 ChannelHandler,分别是:

  • ChannelInBoundHandler(入站事件处理器)
  • ChannelOutBoundHandler(出站事件处理器)

假设每次请求都会触发事件,而由 ChannelHandler 来处理这些事件,这个事件的处理顺序是由 ChannelPipeline 来决定的。

ChannelHanlder 处理,出站/入站的事件

ChannelPipeline 为 ChannelHandler 链提供了容器。到 Channel 被创建的时候,会被 Netty 框架自动分配到 ChannelPipeline 上。

ChannelPipeline 保证 ChannelHandler 按照一定顺序处理事件,当事件触发以后,会将数据通过 ChannelPipeline 按照一定的顺序通过 ChannelHandler。

说白了,ChannelPipeline 是负责“排队”的。这里的“排队”是处理事件的顺序。

同时,ChannelPipeline 也可以添加或者删除 ChannelHandler,管理整个队列。

如上图,ChannelPipeline 使 ChannelHandler 按照先后顺序排列,信息按照箭头所示方向流动并且被 ChannelHandler 处理。

说完了 ChannelPipeline 和 ChannelHandler,前者管理后者的排列顺序。那么它们之间的关联就由 ChannelHandlerContext 来表示了。

每当有 ChannelHandler 添加到 ChannelPipeline 时,同时会创建 ChannelHandlerContext 。

ChannelHandlerContext 的主要功能是管理 ChannelHandler 和 ChannelPipeline 的交互。

 

ChannelHandlerContext 参数贯穿 ChannelPipeline,将信息传递给每个 ChannelHandler,是个合格的“通讯员”。

ChannelHandlerContext 负责传递消息

把上面提到的几个核心组件归纳一下,用下图表示方便记忆他们之间的关系。

Netty 核心组件关系图

Netty 的数据容器

前面介绍了 Netty 的几个核心组件,服务器在数据传输的时候,产生事件,并且对事件进行监控和处理。

接下来看看数据是如何存放以及是如何读写的。Netty 将 ByteBuf 作为数据容器,来存放数据。

ByteBuf 工作原理

从结构上来说,ByteBuf 由一串字节数组构成。数组中每个字节用来存放信息。

ByteBuf 提供了两个索引,一个用于读取数据,一个用于写入数据。这两个索引通过在字节数组中移动,来定位需要读或者写信息的位置。

当从 ByteBuf 读取时,它的 readerIndex(读索引)将会根据读取的字节数递增。

同样,当写 ByteBuf 时,它的 writerIndex 也会根据写入的字节数进行递增。

ByteBuf 读写索引图例

需要注意的是极限的情况是 readerIndex 刚好读到了 writerIndex 写入的地方。

如果 readerIndex 超过了 writerIndex 的时候,Netty 会抛出 IndexOutOf-BoundsException 异常。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

非ban必选

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值