引子

在上一篇《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》里,标题里用到了“魔鬼训练”。一向注重标题贴合内容本意,为什么我会用这个词,这里跟大家解释一下。

这是我自己的一个学习方法。有些朋友是知道的,我日语学的不错的。1年的时间内从0开始学习通过了最高级(一级)的考试。当时我就是用的这种方法。简单介绍一下当时学习的整个过程:

我加入了当时一个特别大(鼎盛时期有8W人)的对日公司。听小伙伴们说有的人3个月就可以过日语三级(最低级别是四级)了。我就想啊,别人要是可以我也可以。于是我一个0基础,直接跳过了四级班,直接报了三级班。

可以想象,每次上课,我如听天书,就是奔着老师的颜值去的。但是私下里,我自己在赶进度。自己找其他人学习了假名等基础知识,然后抱着四级课本从头到尾的捋内容。捋完之后开始捋三级课本。怎么可能看的懂!但是我在三级课本里用到的四级内容的地方,我知道去哪里找了。所以当我终于赶上了老师的进度,三级的东西基本不会,但是四级的内容就差不多了。

我一边跟着老师学,听不明白的,我就找之前的内容自己补习。三个月后的日语考试,在这个班30多个人中,我考了十几名。

我如法炮制,考二级的时候,我在整个公司一起举行的考试中考了第7名。考一级的时候,我是第1名。第2名的分数被我远远的甩在了后面。

于是我的名字那时候经常出现在公司的周刊上面。还有人专门从其他办公楼跑过来看一眼,说是想知道这个传说中的人物长成什么样子。

这整件事情魔鬼在哪里呢,外面只看到了结果。那时候刚大学毕业,大家一起住公司宿舍。那个宿舍有7层楼,只有两层有人住。一层住男生,一层住女生。我下班之后就找没人的楼层自己在那里读日语。除了别人来叫我玩,我很少主动找别人玩的,有时间就学啊。

1年的时间努力一把其实够好几年收益的。我虽然当时努力的方向不对,但是也收益了。头两年我是在沈阳的,来到北京之后找工作去了当时不错的名企,薪资也比同届的其他人高出不少。其实那时候我的技术很菜,但是日语能学的这么好,说明学习能力不错的。年轻可培养嘛,所以人家也很乐意收。

不过这里说明了,努力的方向不对。如果我用来学习技术,那我这几年的整个人生状态都将被改变。但是我花了2年搞日语;花了1年多搞文学;如果不是要生小鲜肉,我大概已经北大文学系研究生毕业了;后来读的是中科院心理学的研究生。爱好,我是认真的。人各有所求,虽然技术上发力的晚,我也不后悔。各阶段自己想做的事情都做了。

简而言之,以上的学习方法推荐给大家,近期的文章安排也是遵循这一思路。另外一点很重要:这不是一个成功的例子,而是一个失败的教训。虽然一直以来平台都不错,还是有希望的。不可否认的是因为我那时候没有好好学习技术,现在步履艰难。换个词大家应该就能理解了:不务正业。

深入理解Channel

引入Channel

在《RabbitMQ设计原理解析》里提到RabbitMQ的工作原理如下:

简单理解一下:在生产端也就是产生消息的客户端。如果咱们使用MQ发送消息,那就是咱们的应用服务器。它会通过Channel和MQ服务器也就是Broker进行通信。在Broker内部有一个路由层也就是Exchange。Exchange是交换机的意思,顾名思义,它的作用是进行一些规则的匹配,根据不同的规则将不同的消息转发到不同的消息队列Queue上。Queue里的消息到达消费端也要进行Channel。在《架构师三大难-领域划分问题》的示例三(异步处理模式)里,我也讲了:消费端和生产端有可能是同一个应用,也可以设计为不同的应用。

好,那在这整个流程中,Channel到底是个什么东西呢?

其实Channel通常翻译为信道或者渠道。不是Linux网络编程里的标准概念,而是一种抽象。什么的抽象呢?文件的读取等操作的抽象。看下面Java NIO中最重要的通道的实现:

  • FileChannel:从文件中读写数据(不可异步读写)

  • DatagramChannel: 能通过UDP读写网络中的数据

  • SocketChannel:能通过TCP读写网络中的数据

  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel

再联想一下上篇《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》里提到的Socket的本质:

socket的本质就是一种类型的文件,所以一个socket在进行读写操作时会对应一个文件描述符fd(file descriptor)。

这一段是让大家回忆一下:socket是一种文件。对应于“文件的读取等操作的抽象”中的文件。

Java中怎么使用Channel

Channel的概念语言无关,这里只是因为本人更熟悉Java。使用别的语言不影响对概念的理解。

上篇文章中用到的ServerSocket和Socket类中都有getChannel的实现:

/*** Returns the unique {@link java.nio.channels.SocketChannel SocketChannel}* object associated with this socket, if any.** <p> A socket will have a channel if, and only if, the channel itself was* created via the {@link java.nio.channels.SocketChannel#open* SocketChannel.open} or {@link* java.nio.channels.ServerSocketChannel#accept ServerSocketChannel.accept}* methods.** @return  the socket channel associated with this socket,*          or {@code null} if this socket was not created*          for a channel** @since 1.4* @spec JSR-51*/
public SocketChannel getChannel() {return null;
}

注释直接翻译成中文:

返回一个唯一的SocketChannel对象或者返回null。一个Socket只有在Channel本身是由SocketChannel.open或者ServerSocketChannel.accept方法创建之时才会存在(不为null)。

上面代码已经很清楚了,直接使用Socket时,getChannel就是返回null。没有用的。那正确的获取SocketChannel的方法是什么呢?来看下面的代码:

@Test
public void getChannel() throws Exception {InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 520);for (int i=1; i <= 2; i++) {SocketChannel socketChannel = SocketChannel.open(inetSocketAddress);String msg = i == 1 ? "客户端:我知道我是任性太任性,伤透了你的心。我是追梦的人,追一生的缘分。" :"客户端:我愿意嫁给你,你却不能答应我。";ByteBuffer writeBuffer = ByteBuffer.allocate(20000);writeBuffer.put(msg.getBytes());writeBuffer.flip();socketChannel.write(writeBuffer);writeBuffer.clear();ByteBuffer readBuffer = ByteBuffer.allocate(20000);readBuffer.flip();socketChannel.read(readBuffer);socketChannel.close();}
}

服务端代码还是用《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》中服务端的代码。因为客户端没有打印任何东西,直接看服务端运行结果:

客户端:我知道我是任性太任性,伤透了你的心。我是追梦的人,追一生的缘分。

服务端:我知道你是任性太任性,伤透了我的心。同是追梦的人,难舍难分。

客户端:我愿意嫁给你,你却不能答应我。

服务端:你愿意嫁给你,我却不能向你承诺。

和上篇文章运行结果一致。

看到这里大家是不是更疑惑了,SocketChannel和Socket做的同样的事情?

public static SocketChannel open(SocketAddress remote) throws IOException {SocketChannel sc = open();try {sc.connect(remote);} catch (Throwable x) {try {sc.close();} catch (Throwable suppressed) {
;}throw x;}assert sc.isConnected();return sc;
}

注意里面标红加粗的三个方法,open方法点进去有实现。这里有只截取open方法中我想说明的地方:

SocketChannelImpl(SelectorProvider var1) throws IOException {super(var1);this.fd = Net.socket(true);this.fdVal = IOUtil.fdVal(this.fd);thisthis.state = 0;
}

结合客户端例子的代码,发现了什么?

这张图眼熟不?《接下来一段时间会对大家进行网络通信的魔鬼训练-理解socket》的图又来了一遍。看!TCP客户端这里做的事情,Channel全都做了!

SocketChannel就是Socket 的替代类, 支持阻塞通信与非阻塞通信。Socket原生只支持最简的阻塞通信。Socket是在java.net下,SocketChannel在java.nio包下。

MQ中怎么使用Channel

下面是RabbitMQ的客户端代码,为什么用RabbitMQ呢?《RabbitMQ设计原理解析》里有说明:因为它实现了AMQP的标准协议。先上代码:

public static final String EXCHANGE_NAME = "RABBITMQ_Fanout";public static void main(String[] args) throws Exception{ConnectionFactory connectionFactory = new ConnectionFactory();connectionFactory.setHost("localhost");Connection connection = connectionFactory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, "fanout");String message = "This message is from Fanout mode.特点是Consumer均可获取到消息";channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());System.out.println("---【Producer发送消息】" + message + "---" );channel.close();;connection.close();}

解析一下这段代码:这是一个生产端的代码。首先新建一个连接工厂的实例。一个连接工厂可以创建多个连接。一个连接可以创建多个Channel信道。Channel可以声明为fanout广播,也可以设置其他路由形式。然后就可以通过Channel进行数据发送了。

在AMQP协议中,有channel的概念,在RabbitMq中,channel表示逻辑连接或者叫虚拟连接,是棣属于TCP连接的。一个TCP连接里可以创建多个channel,在Rabbit MQ里,消息的发送和接收都是基于channel的。

有了TCP连接后,还需要channel的原因如下:

  • 创建和销毁TCP连接很耗时;

  • 打开太多TCP连接,耗操作系统资源,并发量大到一定程度,系统的吞吐量会降低;

  • 使用一个connection多channel的方式,可以提升连接的利用率。

因此采用多个channel多路复用一个TCP连接的方式才比较合理。

https://www.rabbitmq.com/api-guide.html#connection-and-channel-lifspan

说话要有证据,官网上这样说:

Client connections are meant to be long-lived. The underlying protocol is designed and optimized for long running connections. That means that opening a new connection per operation, e.g. a message published, is unnecessary and strongly discouraged as it will introduce a lot of network roundtrips and overhead.

Channels are also meant to be long-lived but since many recoverable protocol errors will result in channel closure, channel lifespan could be shorter than that of its connection. Closing and opening new channels per operation is usually unnecessary but can be appropriate. When in doubt, consider reusing channels first.

Channel-level exceptions such as attempts to consume from a queue that does not exist will result in channel closure. A closed channel can no longer be used and will not receive any more events from the server (such as message deliveries).

翻译一下:

客户端连接是要长久存活的,基础协议(AMQP协议)里做了连接要长久存活的优化。就是说为了发一条消息而打开一个新的连接没有必要,而且强烈不推荐。因为会带来额外的通信开销。

Channel也是长久存活的,但是由于许多可以恢复的错误会导致Channel关闭,所以channel的生命周期会比连接短。每次操作都关闭和打开新Channel,没有必要但是却可以是比较合适的选择。如果不知道怎么做更好的时候,优先复用Channel。

Channel级别的异常比如尝试消费一个不存在的队列会导致Channel关闭。一个关闭的Channel不会再被使用,也不会再收到任何如消息传递之类的服务端事件。

简单来说,Channel是为了复用连接产生的,如果Channel异常了,就可以直接关闭Channel不至于整个连接都关闭,减少开销。

Java的Channel和MQ的Channel比较

本质上,不管是Java的Channel还是MQ的Channel都为了连接的复用而生。所以Java的Channel出现在java.nio包里,做的是io多路复用的事情。只是因为Channel是个逻辑概念,究竟是先产生Channel,还是必须依附于连接由设计者自己说了算。

总结

很多公司都有渠道(Channel)一说,但是不同的场景意义各不相同。比如:业务渠道、支付渠道。本质上,这些渠道的对端各有不同,又希望复用,才产生了网关。从这个意义上来说,网关非常合适用多路复用来解释。

Channel这个概念是理解NIO、netty的基础,也是理解MQ底层通信过程的基础。不管是什么类型的MQ,在创建连接时都需要同时配置channel,只不过有时候使用了约定大于配置,不是显式的。其实这一篇我写了万把字。太长了所以拆成了几篇,看完这几篇,我预期上很多之前看不懂的源会清晰很多。

往期推荐

避免线上故障的10条建议

为什么要持续重构

代码整洁之道--边界

LRU缓存实现(Java)

深入理解MQ生产端的底层通信过程-理解channel相关推荐

  1. ZLL本地局域网通信过程

    Interface_srpcserver -----以灯的状态操作位例 网关与客户端通过Socket API通信,Socket API在socket_server.c中实现,socket_server ...

  2. [以太坊源代码分析] VI. 基于p2p的底层通信(上篇)

    以太坊作为一个去中心化的系统,其底层个体相互间的通信显然非常重要,所有数据的同步,各个个体状态的更新,都依赖于整个网络中每个个体相互间的通信机制.以太坊的网络通信基于peer-to-peer(p2p) ...

  3. MQ、JMS以及ActiveMQ 关系的理解

    MQ.JMS以及ActiveMQ 关系的理解  1.ms 的一个标准或者说是一个协议.  通常用于企业级应用的消息传递. 主要有topic 消息(1 对多), queue 消息(1对1). 2.act ...

  4. 取本地数据_深入理解Kafka服务端之Follower副本如何同步Leader副本的数据

    一.场景分析Kafka采用的是主写主读的方式,即客户端的读写请求都由分区的Leader副本处理,那么Follower副本要想保证和Leader副本数据一致,就需要不断地从Leader副本拉取消息来进行 ...

  5. 对UART、RS232、485通信的理解

    实际上这一篇博文想表达的就是想说清楚什么是串口通信. 先普及一下通信的基本知识点: 数据通信的种类:串行通信.并行通信.不管是什么类型的通信,再怎么复杂的,也是在这两种上面衍生出来的. 许多传输线或者 ...

  6. [笨木头FireFly 03]完整的服务端和客户端通信

    #PS: 其实这篇文件是2013.10.12写完的,一直没发布,因为从那天起,我又跑回去折腾客户端的东西了(打算用Cocos2d-x3.0做下一个游戏),以及我的老游戏的维护和更新.总之各种借口(小若 ...

  7. 正确理解精益生产(zt)

    虽然精益生产已经成为国人熟习的术语,而且至迟己经在1994年,在中国某个大学的一位教授支持下我国某个大型国有制造企业已经宣布实现了精益生产与"一件流".但是,半年后给企业带来的并不 ...

  8. 【Zynq UltraScale+ MPSoC】基于LWIP模板的udp通信与测试(一):网络调试助手和PS端的简单通信

    文章目录 一.前言 二.PL端的配置 三.PS端的程序设计 1.LWIP的UDP服务器模板介绍 readme main.c udp_perf_server platform_zynqmp.c 2.具体 ...

  9. 【Flutter】Flutter 混合开发 ( Flutter 与 Native 通信 | Android 端实现 MethodChannel 通信 )

    文章目录 前言 一.Android 端 MethodChannel 构造函数 二.Android 端 setMethodCallHandler 方法 三.Android 端实现 MethodChann ...

最新文章

  1. installshield学习笔记
  2. BigData之Hadoop:Hadoop的简介、深入理解、下载、案例应用之详细攻略
  3. CentOS中输入yum报错:sudo: unable to execute /bin/yum: No such file or directory
  4. 安装Kerberos服务端和客户端
  5. @程序员,如何在编程面试中脱颖而出?
  6. 华为路由器第三方插件_为什么路由器不开 SSH 就等于失去了很多乐趣?
  7. 利用OpenSSL创建自签名的SSL证书备忘
  8. 使用BarTender连接Excel打印标签
  9. python 打开pdf显示在页面_C# WinForm打开PDF文件并在窗体中显示
  10. 互联网日报 | 360企业安全更名“政企安全”;B站获欢喜传媒独家外部播放权;银联发布首款数字银行卡...
  11. 【ArcGIS微课1000例】0010:ArcGIS影像裁剪(裁剪、掩膜提取)
  12. centos 使用 scl 软件集
  13. 【Java异常】Variable used in lambda expression should be final or effectively final
  14. mysql profiling sending data_Mysql一次Sending data占用大量时间的深入分析优化案例
  15. 分享一个漂亮的后台 admin 前端模板
  16. 假设检验:如何理解单侧、双侧检验的拒绝域
  17. Z字型变幻,整数反转
  18. 哪一种验证方法最好?形式验证、硬件加速还是动态仿真?
  19. 4 anbox 树莓派_Anbox让你在Linux上“原生运行”Android应用
  20. 输入框根据拼音首字母/中文字符联想补全

热门文章

  1. php-sdk 安装,PHP SDK怎么安装
  2. 蘑菇云【行空板Python入门教程】第三课:多功能提醒器
  3. 动力节点Maven课程笔记
  4. 有人熟悉rsoft软件吗,可以交流交流。
  5. 浅谈JAVA适配器模式
  6. pxe启动引导双硬盘中的ssd盘cmos设置
  7. 武汉理工计算机学硕是几年,2021年武汉理工大学计算机技术考研成功经验分享...
  8. 惊呆!编程就像写文档!开发神似搭积木!
  9. 每天一个小实例——使用pdfplumber提取pdf表格及文本,并保存到excel
  10. 能加密的写日记小工具(解压可用,无需安装)