Netty学习二:Netty组件和helleword程序


为什么要用 Netty

Netty的定义

Netty是由JBOSS提供的一个java开源框架,Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序

为什么要用 Netty

1、虽然 JAVA NIO 框架提供了 多路复用 IO 的支持,但是并没有提供上层“信息格式” 的良好封装。例如前两者并没有提供针对 Protocol Buffer、JSON 这些信息格式的封装,但是 Netty 框架提供了这些数据格式封装(基于责任链模式的编码和解码功能);
2、NIO 的类库和 API 相当复杂,使用它来开发,需要非常熟练地掌握 Selector、ByteBuffer、ServerSocketChannel、SocketChannel 等,需要很多额外的编程技能来辅助使用 NIO,例如,因为 NIO 涉及了Reactor 线程模型,所以必须必须对多线程和网络编程非常熟悉才能写出高质量的 NIO 程序;
3、要编写一个可靠的、易维护的、高性能的 NIO 服务器应用。除了框架本身要兼容实现各类操作系统的实现外。更重要的是它应该还要处理很多上层特有服务,例如:客户端的权限、还有上面提到的信息格式封装、简单的数据读取,断连重连,半包读写,心跳等等, 这些 Netty 框架都提供了响应的支持;
4、Netty 的性能很高,按照 Facebook 公司开发小组的测试表明,Netty 最高能达到接近百万的吞吐:

HelloWorld程序

服务端


public class NettyServerDemo {

	public static void main(String[] args) throws InterruptedException {

		System.out.println("服务器开始启动");
		(new NettyServerDemo()).start();
		System.out.println("服务器关闭");
	}

	private void start() throws InterruptedException {

		final NettyServerHandler wNettyServerHandler = new NettyServerHandler();
		// 线程组
		EventLoopGroup wEventLoopGroup = new NioEventLoopGroup();
		// 服务端启动器
		ServerBootstrap wServerBootstrap = new ServerBootstrap();
		try {
			wServerBootstrap
					.group(wEventLoopGroup)
					.channel(NioServerSocketChannel.class) // 指定使用Nio通讯模式
					.localAddress(NettyConstant.DEFAULT_PORT) // 指定监听端口
					.childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel socketChannel) throws Exception {
							socketChannel.pipeline().addLast(wNettyServerHandler);
						}
					});

			// 异步绑定服务器, sync会阻塞直到完成。
			ChannelFuture wChannelFuture = wServerBootstrap.bind().sync();
			// 阻塞了当前线程,直到服务器的Channel被关闭
			wChannelFuture.channel().closeFuture().sync();
		} finally {
			wEventLoopGroup.shutdownGracefully().sync();
		}
	}
}



/*允许ChannelHandler在多个ChannelPipeline中进行共享*/
@ChannelHandler.Sharable
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

		ByteBuf wByteBuf = (ByteBuf) msg;
		System.out.println("server accept, message: " + wByteBuf.toString(CharsetUtil.UTF_8));
		ctx.writeAndFlush(wByteBuf);
		ctx.close();
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

		cause.printStackTrace();
		ctx.close();
	}
}

客户端


public class NettyClientDemo {

	public static void main(String[] args) throws InterruptedException {

		(new NettyClientDemo()).start();
	}

	private void start() throws InterruptedException {

		final NettyClientHandler wNettyClientHandler = new NettyClientHandler();
		// 线程池
		EventLoopGroup wEventLoopGroup = new NioEventLoopGroup();
		// 客户端启动器
		Bootstrap wBootstrap = new Bootstrap();
		try {
			wBootstrap
					.group(wEventLoopGroup)
					.channel(NioSocketChannel.class) // 指定使用Nio通讯模式
					.remoteAddress(new InetSocketAddress(NettyConstant.DEFAULT_SERVER_IP, NettyConstant.DEFAULT_PORT)) // 对端Ip和端口
					.handler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel socketChannel) throws Exception {
							socketChannel.pipeline().addLast(wNettyClientHandler);
						}
					});
			// 异步连接到服务器, sync会阻塞直到完成。
			ChannelFuture wChannelFuture = wBootstrap.connect().sync();
			// 阻塞了当前线程,直到服务器的Channel被关闭
			wChannelFuture.channel().closeFuture().sync();
		} finally {
			wEventLoopGroup.shutdownGracefully().sync();
		}
	}
}


public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

	/**
	 * 读取到返回数据后进行业务处理
	 */
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {

		System.out.println("client Accept, message: " + msg.toString(CharsetUtil.UTF_8));
	}

	/**
	 * channel 活跃后, 做业务处理
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {

		ctx.writeAndFlush(Unpooled.copiedBuffer("HelloWorld", CharsetUtil.UTF_8));
	}
}

设置的常量

public class NettyConstant {

	public static final Integer DEFAULT_PORT = 9999;
	public static final String DEFAULT_SERVER_IP = "127.0.0.1";
}

Netty组件

Channel 接口

基本的 I/O 操作(bind()、connect()、read()和 write())依赖于底层网络传输所提供的原语。在基于 Java 的网络编程中,其中基本的构造是类 Socket。Netty 的 Channel 接口所提供的 API,被用于所有的 I/O 操作。大大地降低了直接使用 Socket 类的复杂性。

Channel 的生命周期状态

ChannelUnregistered:Channel 已经被创建,但还未注册到 EventLoop
ChannelRegistered:Channel 已经被注册到了 EventLoop
ChannelActive:Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了
ChannelInactive:Channel 没有连接到远程节点

EventLoop 和 EventLoopGroup

EventLoop

  • 在上一章,我们在 NIO 中在一个 while 循环中 select 处理事件,然后依次处理每种事件。我们可以把它称为事件循环,这就是 EventLoop。interface io.netty.channel. EventLoop 定义了 Netty 的核心抽象,用于处理网络连接的生命周期中所发生的事件。
  • 一个 EventLoop 将由一个永远都不会改变的 Thread 驱动,同时任务(Runnable 或者 Callable)可以直接提交给EventLoop 实现,以立即执行或者调度执行。
  • 根据配置和可用核心的不同,可能会创建多个 EventLoop 实例用以优化资源的使用,并且单个 EventLoop 可能会被指派用于服务多个 Channel,一个EventLoop 就会有一个Seletor

线程管理

在内部,当提交任务到如果 (当前)调用线程正是支撑 EventLoop 的线程,那么所提交的代码块将会被(直接)执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当 EventLoop 下次处理它的事件时,它会执行队列中的那些任务/事件。

ChannelFuture 接口

Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了 ChannelFuture 接口,其 addListener()方法注册了一个 ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。

可以将 ChannelFuture 看作是将来要执行的操作的结果的占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯定的是它将会被执行。

ChannelHandler

ChannelHandler 接口

从应用程序开发人员的角度来看,Netty 的主要组件是 ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 的方法是由网络事件触发的。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,例如各种编解码,或者处理转换过程中所抛出的异常。

Netty 定义了下面两个重要的 ChannelHandler 子接口:
     ChannelInboundHandler ——处理入站数据以及各种状态变化;
     ChannelOutboundHandler ——处理出站数据并且允许拦截所有的操作。

ChannelHandler 子类图

ChannelPipeline 和 ChannelHandlerContext

ChannelPipeline 接口

当 Channel 被创建时,它将会被自动地分配一个新的 ChannelPipeline。这项关联是永久性的;Channel 既不能附加另外一个 ChannelPipeline,也不能分离其当前的。在 Netty 组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。

使得事件流经 ChannelPipeline 是 ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个 ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的。

ChannelHandler 在ChannelPipeline的生命周期

在 ChannelHandler 被添加到 ChannelPipeline 中或者被从 ChannelPipeline 中移除时会调用下面这些方法。这些方法中的每一个都接受一个 ChannelHandlerContext 参数。

handlerAdded 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用
handlerRemoved 当从 ChannelPipeline 中移除 ChannelHandler 时被调用
exceptionCaught当处理过程中在 ChannelPipeline 中有错误产生时被调用

ChannelPipeline 中 ChannelHandler

ChannelHandlerContext

通过使用作为参数传递到每个方法的 ChannelHandlerContext,事件可以被传递给当前ChannelHandler 链中的下一个 ChannelHandler。虽然这个对象可以被用于获取底层的Channel,但是它主要还是被用于写出站数据。

ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的关联,每当有ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext。ChannelHandlerContext 的主要功能是管理它所关联的 ChannelHandler 和在同一个ChannelPipeline 中的其他 ChannelHandler 之间的交互。

ChannelHandlerContext、channel、ChannelPipeline, write方法的区别

Bootstrap和ServerBootstrap

Bootstrap用于客户端,ServerBootstrap用于服务端。ServerBootstrap 将绑定到一个端口,因为服务器必须要监听连接,而 Bootstrap 则是由想要连接到远程节点的客户端应用程序所使用的。

引导一个客户端只需要一个 EventLoopGroup,但是一个ServerBootstrap 则需要两个。因为服务器需要两组不同的 Channel。第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel

ChannelOption

ChannelOption 的各种属性在套接字选项中都有对应。

1、ChannelOption.SO_BACKLOG 对应的是 tcp/ip 协议 listen 函数中的 backlog 参数,函数listen(int socketfd,int backlog)用来初始化服务端可连接队列,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定了队列的大小

2、ChannelOption.SO_REUSEADDR 对应于套接字选项中的 SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口。 比如,某个服务器进程占用了 TCP 的 80 端口进行监听,此时再次监听该端口就会返回错误,使用该参数就可以解决问题,该参数允许共用该端口,这个在服务器程序中比较常使用,比如某个进程非正常退出,该程序占用的端口可能要被占用一段时间才能允许其他进程使用,而且程序死掉以后,内核一需要一定的时间才能够释放此端口,不设置 SO_REUSEADDR就无法正常使用该端口。

3、ChannelOption.SO_KEEPALIVE 参数对应于套接字选项中的 SO_KEEPALIVE,该参数用于设置 TCP 连接,当设置该选项以后,连接会测试链接的状态,这个选项用于可能长时间没有数据交流的连接。当设置该选项以后,如果在两小时内没有数据的通信时,TCP 会自动发送一个活动探测数据报文。

4、ChannelOption.SO_SNDBUF 和 ChannelOption.SO_RCVBUF 。ChannelOption.SO_SNDBUF 参数对应于套接字选项中的 SO_SNDBUF,ChannelOption.SO_RCVBUF 参数对应于套接字选项中的 SO_RCVBUF 这两个参数用于操作接收缓冲区和发送缓冲区的大小,接收缓冲区用于保存网络协议站内收到的数据,直到应用程序读取成功,发送缓冲区用于保存发送数据,直到发送成功。

5、ChannelOption.SO_LINGER 参数对应于套接字选项中的SO_LINGER,Linux内核默认的处理方式是当用户调用 close()方法的时候,函数返回,在可能的情况下,尽量发送数据,不一定保证会发生剩余的数据,造成了数据的不确定性,使用 SO_LINGER 可以阻塞 close()的调用时间,直到数据完全发送

6、ChannelOption.TCP_NODELAY 参数对应于套接字选项中的 TCP_NODELAY,该参数的使用与 Nagle 算法有关,Nagle 算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,虽然该方式有效提高网络的有效负载,但是却造成了延时,而该参数的作用就是禁止使用 Nagle 算法,使用于小数据即时传输,于 TCP_NODELAY 相对应的是 TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。

ByteBuf

使用模式

1、堆缓冲区 。最常用的 ByteBuf 模式是将数据存储在 JVM 的堆空间中。这种模式被称为支撑数组(backing array),它能在没有使用池化的情况下提供快速的分配和释放。可以由 hasArray()来判断检查 ByteBuf 是否由数组支撑。如果不是,则这是一个直接缓冲区。

2、直接缓冲区 。从JVM占用内存外分配一块内存,分配和释放都较为昂贵,但读写效率相对较高。

3、复合缓冲区 。它为多个 ByteBuf 提供一个聚合视图。比如 HTTP 协议,分为消息头和消息体,这两部分可能由应用程序的不同模块产生,各有各的 ByteBuf,将会在消息被发送的时候组装为一个 ByteBuf,此时可以将这两个 ByteBuf 聚合为一个CompositeByteBuf,然后使用统一和通用的 ByteBuf API 来操作。

分配

1、ByteBufAllocator接口 。Netty 提供了两种 ByteBufAllocator 的实现:PooledByteBufAllocator 和Unpooled-ByteBufAllocator。前者池化了 ByteBuf 的实例以提高性能并最大限度地减少内存碎片。后者的实现不池化 ByteBuf 实例,并且在每次它被调用时都会返回一个新的实例。

2、Unpooled 缓冲区 ,一个Netty提供的工具类。提供了静态的辅助方法来创建未池化的 ByteBuf 实例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值