netty系列之:channel和channelGroup
简介
channel是netty中数据传输和数据处理的渠道,也是netty程序中不可或缺的一环。在netty中channel是一个接口,针对不同的数据类型或者协议channel会有具体的不同实现。
虽然channel很重要,但是在代码中确实很神秘,基本上我们很少能够看到直接使用channel的情况,那么事实真的如此吗?和channel相关的ChannelGroup又有什么作用呢?一起来看看吧。
神龙见首不见尾的channel
其实netty的代码是有固定的模板的,首先根据是server端还是client端,然后创建对应的Bootstrap和ServerBootstrap。然后给这个Bootstrap配置对应的group方法。然后为Bootstrap配置channel和handler,最后启动Bootstrap即可。
这样一个标准的netty程序就完成了。你需要做的就是为其挑选合适的group、channel和handler。
我们先看一个最简单的NioServerSocketChannel的情况:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChatServerInitializer());
b.bind(PORT).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
这里,我们将NioServerSocketChannel设置为ServerBootstrap的channel。
这样就完了吗?channel到底是在哪里用到的呢?
别急,我们仔细看一下try block中的最后一句:
b.bind(PORT).sync().channel().closeFuture().sync();
b.bind(PORT).sync()实际上返回了一个ChannelFuture对象,通过调用它的channel方法,就返回了和它关联的Channel对象。
然后我们调用了channel.closeFuture()方法。closeFuture方法会返回一个ChannelFuture对象,这个对象将会在channel关闭的时候收到通知。
而sync方法会实现同步阻塞,一直等到channel关闭为止,从而进行后续的eventGroup的shutdown操作。
在ServerBootstrap中构建模板中,channel其实有两个作用,第一个作用是指定ServerBootstrap的channel,第二个作用就是通过channel获取到channel关闭的事件,最终关闭整个netty程序。
虽然我们基本上看不到channel的直接方法调用,但是channel毋庸置疑,它就是netty的灵魂。
接下来我们再看一下具体消息处理的handler的基本操作:
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// channel活跃
ctx.write("Channel Active状态!\r\n");
ctx.flush();
}
通常如果需要在handler中向channel写入数据,我们调用的是ChannelHandlerContext的write方法。这个方法和channel有什么关系呢?
首先write方法是ChannelOutboundInvoker接口中的方法,而ChannelHandlerContext和Channel都继承了ChannelOutboundInvoker接口,也就是说,ChannelHandlerContext和Channel都有write方法:
ChannelFuture write(Object msg);
因为这里我们使用的是NioServerSocketChannel,所以我们来具体看一下NioServerSocketChannel中write的实现。
经过检查代码我们会发现NioServerSocketChannel继承自AbstractNioMessageChannel,AbstractNioMessageChannel继承自AbstractNioChannel,AbstractNioChannel继承自AbstractChannel,而这个write方法就是AbstractChannel中实现的:
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
Channel的write方法,实际上调用了pipeline的write方法。下面是pipeLine中的write方法:
public final ChannelFuture write(Object msg) {
return tail.write(msg);
}
这里的tail是一个AbstractChannelHandlerContext对象。
这样我们就得出了这样的结论:channel中的write方法最终实际上调用的是ChannelHandlerContext中的write方法。
所以上面的:
ctx.write("Channel Active状态!\r\n");
实际上可以直接从channel中调用:
Channel ch = b.bind(0).sync().channel();
// 将消息写入channel中
ch.writeAndFlush("Channel Active状态!\r\n").sync();
channel和channelGroup
channel是netty的灵魂,对于Bootstrap来说,要获取到对应的channel,可以通过调用:
b.bind(PORT).sync().channel()
来得到,从上面代码中我们也可以看到一个Bootstrap只会对应一个channel。
channel中有一个parent()方法,用来返回它的父channel,所以channel是有层级结构的,
我们再来看一下channelGroup的定义:
public interface ChannelGroup extends Set<Channel>, Comparable<ChannelGroup>
可以看到ChannelGroup实际上是Channel的集合。ChannelGroup用来将类似的Channel构建成集合,从而可以对多个channel进行统一的管理。
可以能有小伙伴要问了,一个Bootstrap不是只对应一个channel吗?那么哪里来的channel的集合?
事实上,在一些复杂的程序中,我们可能启动多个Bootstrap来处理不同的业务,所以相应的就会有多个channel。
如果创建的channel过多,并且这些channel又是很同质化的时候,就有需求对这些channel进行统一管理。这时候就需要用到channelGroup了。
channelGroup的基本使用
先看下channelGroup的基本使用,首先是创建一个channelGroup:
ChannelGroup recipients =
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
有了channelGroup之后,可以调用add方法,向其中添加不同的channel:
recipients.add(channelA);
recipients.add(channelB);
并且还可以统一向这些channel中发送消息:
recipients.write(Unpooled.copiedBuffer(
"这是从channelGroup中发出的统一消息.",
CharsetUtil.UTF_8));
基本上channelGroup提供了write,flush,flushAndWrite,writeAndFlush,disconnect,close,newCloseFuture等功能,用于对集合中的channel进行统一管理。
如果你有多个channel,那么可以考虑使用channelGroup。
另外channelGroup还有一些特性,我们来详细了解一下。
将关闭的channel自动移出
ChannelGroup是一个channel的集合,当然我们只希望保存open状态的channel,如果是close状态的channel,还要手动从ChannelGroup中移出的话实在是太麻烦了。
所以在ChannelGroup中,如果一个channel被关闭了,那么它会自动从ChannelGroup中移出,这个功能是怎么实现的呢?
先来看下channelGroup的add方法:
public boolean add(Channel channel) {
ConcurrentMap<ChannelId, Channel> map =
channel instanceof ServerChannel? serverChannels : nonServerChannels;
boolean added = map.putIfAbsent(channel.id(), channel) == null;
if (added) {
channel.closeFuture().addListener(remover);
}
if (stayClosed && closed) {
channel.close();
}
return added;
}
可以看到,在add方法中,为channel区分了是server channel还是非server channel。然后根据channel id将其存入ConcurrentMap中。
如果添加成功,则给channel添加了一个closeFuture的回调。当channel被关闭的时候,会去调用这个remover方法:
private final ChannelFutureListener remover = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
remove(future.channel());
}
};
remover方法会将channel从serverChannels或者nonServerChannels中移出。从而保证ChannelGroup中只保存open状态的channel。
同时关闭serverChannel和acceptedChannel
虽然 ServerBootstrap的bind方法只会返回一个channel,但是对于server来说,可以有多个worker EventLoopGroup,所以当客户端和服务器端建立连接之后建立的accepted Channel是server channel的子channel。
也就是说一个服务器端有一个server channel和多个accepted channel。
那么如果我们想要同时关闭这些channel的话, 就可以使用ChannelGroup的close方法。
因为如果Server channel和非Server channel在同一个ChannelGroup的话,所有的IO命令都会先发给server channel,然后才会发给非server channel。
所以我们可以将Server channel和非Server channel统统加入同一个ChannelGroup中,在程序的最后,统一调用ChannelGroup的close方法,从而达到该目的:
ChannelGroup allChannels =
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public static void main(String[] args) throws Exception {
ServerBootstrap b = new ServerBootstrap(..);
...
b.childHandler(new MyHandler());
// 启动服务器
b.getPipeline().addLast("handler", new MyHandler());
Channel serverChannel = b.bind(..).sync();
allChannels.add(serverChannel);
... 等待shutdown指令 ...
// 关闭serverChannel 和所有的 accepted connections.
allChannels.close().awaitUninterruptibly();
}
public class MyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 将accepted channel添加到allChannels中
allChannels.add(ctx.channel());
super.channelActive(ctx);
}
}
ChannelGroupFuture
另外,和channel一样,channelGroup的操作都是异步的,它会返回一个ChannelGroupFuture对象。
我们看下ChannelGroupFuture的定义:
public interface ChannelGroupFuture extends Future<Void>, Iterable<ChannelFuture>
可以看到ChannelGroupFuture是一个Future,同时它也是一个ChannelFuture的遍历器,可以遍历ChannelGroup中所有channel返回的ChannelFuture。
同时ChannelGroupFuture提供了isSuccess,isPartialSuccess,isPartialFailure等方法判断命令是否部分成功。
ChannelGroupFuture还提供了addListener方法用来监听具体的事件。
总结
channel是netty的核心,当我们有多个channel不便进行管理的时候,就可以使用channelGroup进行统一管理。
本文已收录于 http://www.flydean.com/04-1-netty-channel-group/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
netty系列之:channel和channelGroup的更多相关文章
- netty系列之:channel,ServerChannel和netty中的实现
目录 简介 channel和ServerChannel netty中channel的实现 AbstractChannel和AbstractServerChannel LocalChannel和Loca ...
- 5. 彤哥说netty系列之Java NIO核心组件之Channel
你好,我是彤哥,本篇是netty系列的第五篇. 简介 上一章我们一起学习了如何使用Java原生NIO实现群聊系统,这章我们一起来看看Java NIO的核心组件之一--Channel. 思维转变 首先, ...
- netty系列之:netty中的Channel详解
目录 简介 Channel详解 异步IO和ChannelFuture Channel的层级结构 释放资源 事件处理 总结 简介 Channel是连接ByteBuf和Event的桥梁,netty中的Ch ...
- Netty 系列八(基于 WebSocket 的简单聊天室).
一.前言 之前写过一篇 Spring 集成 WebSocket 协议的文章 —— Spring消息之WebSocket ,所以对于 WebSocket 协议的介绍就不多说了,可以参考这篇文章.这里只做 ...
- Netty 源码 Channel(二)核心类
Netty 源码 Channel(二)核心类 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一.Channel 类图 二. ...
- Netty 源码 Channel(二)主要类
Netty 源码 Channel(二)主要类 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一.Channel 类图 二. ...
- Netty 源码 Channel(一)概述
Netty 源码 Channel(一)概述 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) Channel 为 Netty ...
- Netty 系列目录
Netty 系列目录 二 Netty 源码分析(4.1.20) 1.1 Netty 源码(一)Netty 组件简介 2.1 Netty 源码(一)服务端启动 2.2 Netty 源码(二)客户端启动 ...
- Netty系列(四)TCP拆包和粘包
Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...
随机推荐
- 在 CentOS 7 上安装和配置 Puppet
1 准备 2台 centos7 (master/server:192.168.1.103 agent/client:192.168.1.106) 分别添加puppet自定义仓库 https://yum ...
- centos7 重定向符号
这篇只记录使用对应的使用方法,原理后期更新 >> 追加输出 <<追加输入 >输出 <输入 将正确和错误信息同事保留到一个文件 echo "this is ...
- POJ2115C Looooops
http://poj.org/problem?id=2115 k位储存特点,一旦溢出,那么就到第二个循环开始返回0重新计数.问题实际转化成a+cx=b(mod 2^k)跑多少圈能够重合.因为是k位无符 ...
- kafka时间轮简易实现(二)
概述 上一篇主要介绍了kafka时间轮源码和原理,这篇主要介绍一下kafka时间轮简单实现和使用kafka时间轮.如果要实现一个时间轮,就要了解他的数据结构和运行原理,上一篇随笔介绍了不同种类的数据结 ...
- 纯手画WinForm的Alert提示弹出框
纯手画WinForm的Alert弹框 一.窗体设置 设置以下属性: 属性名 属性值 说明 AutoScaleMode None 确定屏幕分辨率或字体更改时窗体如何缩放 BackColor 103, 1 ...
- 记一次简单的Oracle离线数据迁移至TiDB过程
背景 最近在支持一个从Oracle转TiDB的项目,为方便应用端兼容性测试需要把Oracle测试环境的库表结构和数据同步到TiDB中,由于数据量并不大,所以怎么方便怎么来,这里使用CSV导出导入的方式 ...
- AOP操作-AspectJ注解
AOP操作(AspectJ注解) 1,创建类,在类里面定义方法 2,创建增强类(编写增强逻辑) (1)在增强类里面,创建方法,让不同方法代表不同通知类型 3,进行通知的配置 (1)在spring配置文 ...
- setuid setgid stick bit 特殊权限 粘滞位
1.setuid与setgid讲解 看一下系统中用到它的地方,以/etc/passwd和/usr/bin/passwd为例: 分析一下,/etc/passwd的权限为 -rw-r--r-- 也就是说: ...
- Kubernetes-Secret
1. 简介 Secret 是一种包含少量敏感信息例如密码.令牌或密钥的对象. 这样的信息可能会被放在 Pod 规约中或者镜像中. 使用 Secret 意味着你不需要在应用程序代码中包含机密数据. 由于 ...
- nextcloud个人云搭建
nextcloud个人云搭建 目录 nextcloud个人云搭建 树莓派安装系统 安装OMV5 安装dockcer 挂载硬盘进行映射(使用u盘测试的) 不足 配置数据库 使用docker拉取postg ...