系统间通信方式之(Java之Netty初步详解)(七)
来源:互联网 发布:drugbank数据库 编辑:程序博客网 时间:2024/06/03 20:36
上篇文章我们讨论了Netty的基本原理,重要概念,并使用java代码描述了Netty的基本使用。当然Netty的技术涵盖点远远不是那一篇基础代码就可以全部概括的,但是至少可以给读者一个切入点。让大家去思考一个我们一直在讨论的问题:为什么有了JAVA NIO框架后我们还需要有Netty这样的框架对底层再次进行封装?
5-1、IO模型的封装
5-1-1、再次总结IO模型
在前文中我们已经提到了,几种典型的IO模型(参见系统间通信3、4、5这三篇文章中的介绍,这里再进行一次总结):
阻塞和非阻塞:这个概念是针对应用程序而言,是指应用程序中的线程在向操作系统发送IO请求后,是否一直等待操作系统的IO响应。如果是,那么就是阻塞式的;如果不是,那么应用程序一般会以轮询的方式以一定周期询问操作系统,直到某次获得了IO响应为止(轮序间隔应用程序线程可以做一些其他工作)。
同步和异步:IO操作都是由操作系统进行的(这里的IO操作是个广泛概念了:磁盘IO、网络IO都算),不同的操作系统对不同设备的IO操作都有不同的模式。同步和异步这两个概念都指代的操作系统级别,同步IO是指操作系统和设备进行交互时,必须等待一次完整的请求-响应完成,才能进行下一次操作(当然操作系统和设备本身也有很多技术加快这个反应过程,例如“磁盘预读”技术、数据缓存技术);异步IO是指操作系统和设备进行交互时,不必等待本次得到响应,就可以直接进行下一次操作请求。设备处理完某次请求后,会主动给操作系统相应的响应通知。
多路复用IO:多路复用IO,从本质上看还是一种同步IO,因为它没有100%消除IO_WAIT,操作系统也没有为它提供“主动通知”机制。但是多路复用IO的处理速度已经相当快了,利用设备执行IO操作的时间,操作系统可以继续执行IO请求。并同样采用周期性轮询的方式,获取一批IO操作请求的执行响应。操作系统支持的多路复用IO技术主要有select、poll、epoll、kqueue。
阻塞式同步IO模型:这个从字面上就很好理解了,应用程序请求IO操作,并一直等待处理结果;操作系统同时也进行IO操作,并等待设备的处理结果;可以看出,应用程序的请求线程和操作系统的内核线程都是等待状态。
非阻塞式同步IO模型:应用程序请求IO,并且不用一直等待返回结果就去做其他事情。隔一定的周期,再去询问操作系统上次IO操作有没有结果,直到某一次询问从操作系统拿到IO结果;操作系统内核线程在进行IO操作时,还是处理一直等待设备返回操作结果的状态。
非阻塞式多路复用IO模型:应用程序请求IO的工作采用非阻塞方式进行;操作系统采用多路复用模式工作。
非阻塞式异步IO模型:应用程序请求IO的工作采用非阻塞方式进行,但是不需要轮询了,因为操作系统异步IO其中一个主要特性就是:可以在有IO响应结果的时候,主动进行通知。
5-1-2、对IO模型的再次封装
以上这些IO工作模型,在JAVA中都能够找到对应的支持:传统的JAVA Socket套接字支持阻塞/非阻塞模式下的同步IO(有的技术资料里面也称为OIO或者BIO);JAVA NIO框架在不同操作系统下支持不同种类的多路复用IO技术(windows下的select模型、Linux下的poll/epoll模型);JAVA AIO框架支持异步IO(windows下的IOCP和Linux使用epoll的模拟AIO)
实际上Netty是对JAVA BIO 、JAVA NIO框架的再次封装。让我们不再纠结于选用哪种底层实现。您可以理解成Netty/MINA 框架是一个面向上层业务实现进行封装的“业务层”框架。而JAVA Socket框架、JAVA NIO框架、JAVA AIO框架更偏向于对下层技术实现的封装,是面向“技术层”的框架。
5-2、数据信息格式的封装
“技术层”框架本身只对IO模型技术实现进行了封装,并不关心IO模型中流淌的数据格式;“业务层”框架对数据格式也进行了处理,让我们可以抽出精力关注业务本身。
Protobuf数据协议的集成:Netty利用自身的Channelpipeline的设计(在《架构设计:系统间通信(6)——IO通信模型和Netty 上篇》中讲过),对Protobuf数据协议进行了无缝结合。
JBoss Marshalling数据协议的集成:JBoss Marshalling 是一个Java对象的序列化API包,修正了JDK自带的序列化包的很多问题,又保持跟 java.io.Serializable 接口的兼容。Netty通过封装这个协议,可以帮助我们在客户端-服务端简便的进行对象系列化和反序列化。
HTTP Request / HTTP Response 协议的集成:在Netty中,可以方便的接受和发送Http协议。也就是说,我们可以使用Netty搭建自己的WEB服务器,当然您还可以根据自己的业务要求,方便的设计类似于Struts、Spring MVC这样的WEB框架。
下面是一个使用Netty的Http编码/解码处理器,设计的一个简单的WEB服务器:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
由于上篇文章中已经介绍了Netty的基本使用方法,所以以上的代码将其他不必要的注释、方法都去掉了,只做了实现web服务器的最简代码。但是这段代码本省是可以运行的。下面是运行效果:
5-3、解决了“技术层”框架中的技术问题
通过阅读Netty框架的代码,我们知道了Netty框架至少解决了JAVA NIO框架中的一些Bug:
- JDK-6427854 : (se) NullPointerException in Selector.open()。http://bugs.java.com/view_bug.do?bug_id=6427854。这个Bug的官方描述是:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
这个问题在Netty框架中,负责进行JAVA NIO Selector的NioEventLoop类中得到了解决。
- workaround the infamous epoll 100% CPU bug。http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933。这个Bug出现在linux系统环境,大致是说JAVA NIO 框架在实现 Linux内核 kernel 2.6+中的epoll模型时。Selector.select(timeout)方法不能阻塞指定的timeout时间,导致CPU 100%的情况:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
这个问题从官方的Bug Database中的描述看,是在JDK7的版本中被解决的。Netty框架在JDK 6+的环境下在JAVA NIO框架封装之上解决了这个Bug。
5-4、半包和粘包问题
我们考虑一下这样的情况:我们编写了一个机器人控制程序,通过一个遥控器(客户端)向机器人(服务器)建立了一个长连接,并通过这个连接连续不断的从遥控器发送控制指令给机器人。由于是连续控制指令,所以指令与指令之间没有间隔(实际上您还可以想想很多类似场景,例如:开发的Online对战游戏)。
我们使用JSON格式作为指令数据的承载格式。那么发送方和接收方的数据发送-接受过程可能如下图所示。
通过上图我们看到了接收方为了接受这两条连贯的指令,一共做了三次接受,第二次接收的时候,收到了一部分message1的内容和一部分message2的内容。这里要说明几个注意事项:
MSS:MSS属性是TCP连接双方在三次握手时所确认的每一个TCP报文段中数据字段的最大长度。注意,一是连接双方协商出来的;二是只是数据段的最大长度,不包括IP协议头和TCP协议头的最大长度。
半包是指接收方应用程序在接收信息时,没有接收到一个完成的信息格式块;粘包是指,接收方应用程序在接受信息时,除了接收到发送方应用程序发送的某一个完整数据信息描述外,还接受到了一下发送方应用程序发送的下一个数据信息的一部分。
半包和粘包是针对应用程序来说的,这个问题只会发生在TCP一些进行连续发送数据时(TCP长连接)。UDP不会出现这个问题,因为UDP都是有边界的数据报;TCP短连接也不会出现,因为发送完一个指令信息后连接就断开了,不会发送第二个指令数据。
半包和粘包问题产生的根本是因为TCP本质上没有“数据块”的概念,而是一连串的数据流。在应用程序层面上我们所定义的“数据块”在TCP层面上并不被协议认可。
半包/粘包是一个应用层问题。要解决半包/粘包问题,就是在应用程序层面建立协商一致的信息还原依据。常见的有两种方式:一是消息定长,即保证每一个完整的信息描述的长度都是一定的,这样无论TCP/IP协议如何进行分片,数据接收方都可以按照固定长度进行消息的还原。二是在完整的一块数据结束后增加协商一致的分隔符(例如增加一个回车符)。
在JAVA NIO技术框架中,半包和粘包问题我们需要自己解决,如果使用Netty框架,它其中提供了多种解码器的封装帮助我们解决半包和粘包问题。甚至针对不同的数据格式,Netty都提供了半包和粘包问题的现成解决方式,例如之前我们提到的ProtobufVarint32FrameDecoder解码器,就是专门解决Protobuf数据格式在TCP长连接传输时的半包问题的。
下文中我们会介绍FixedLengthFrameDecoder、DelimiterBasedFrameDecoder、LineBasedFrameDecoder来解决半包/粘包的问题。
由于上文中我们已经通过完整的代码演示了Netty的基本使用,所以下面的示例代码中为了节约篇幅,我只会列出重要的代码片段。
5-4-1、使用FixedLengthFrameDecoder解决问题
FixedLengthFrameDecoder解码处理器将TCP/IP的数据按照指定的长度进行重新拆分,如果接收到的数据不满足设置的固定长度,Netty将等待新的数据到达:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Netty上层的channelRead事件方法将在Channel接收到20个字符的情况下被触发;而如果剩余的内容不到20个字符,channelRead方法将不会被触发(但注意channelReadComplete方法会触发的啦)。
5-4-2、使用LineBasedFrameDecoder解决问题
LineBasedFrameDecoder解码器,基于最简单的“换行符”进行接收到的信息的再组织。windows和linux两个操作系统中的“换行符”是不一样的,LineBasedFrameDecoder解码器都支持。当然这个解码器没有我们后面介绍的DelimiterBasedFrameDecoder解码器灵活。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
那么如果客户端发送的数据是:
this is 0 client \r\n request 1 \r\n”
那么接收方重新通过“换行符”重新组织后,将分两次接受到数据:
this is 0 client
request 1
5-4-3、使用DelimiterBasedFrameDecoder解决问题
DelimiterBasedFrameDecoder是按照“自定义”分隔符(也可以是“回车符”或者“空字符”注意windows系统中和linux系统中“回车符”的表示是不一样的)进行信息的重新拆分。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
DelimiterBasedFrameDecoder有三个参数,这里介绍一下:
- 1
maxFrameLength:最大分割长度,如果接收方在一段长度 大于maxFrameLength的数据段中,没有找到指定的分隔符,那么这个处理器会抛出TooLongFrameException异常。
stripDelimiter:这个是一个布尔型参数,指代是否保留指定的分隔符。
delimiters:设置的分隔符。一般使用Delimiters.lineDelimiter()或者Delimiters.nulDelimiter()。当然您也可以自定义分隔符,定义成bytebuf的类型就行了。
5-5、专注于业务
Netty框架的特性,使我们不需要关心下层所工作的IO模型,利用Netty提供的面向事件驱动的方法结构,使我们更能集中精力关注应用层业务。
在这5篇文章中,我们重点介绍了几种典型的IO模型,并且介绍了JAVA语言对这几种IO模型的实现,最后我们简单介绍了一下Netty框架,并且比较了JAVA NIO框架和Netty框架侧重点。实际上这几篇文章我们讲述的问题只有一个“信息如何进行传递”。
从下一篇文章开始,我们开始介绍JAVA RMI技术,并从JAVA RMI技术引出一项系统间通信重要的技术RPC,我们还会降到RPC的重要实现 TaoBao-Dubbo框架,最后我们讲解ESB技术和几个典型的ESB实现。这些技术要解决的问题是“传递过程如果进行统筹管理”。
- 系统间通信方式之(Java之Netty初步详解)(七)
- 系统间通信方式之(Java之Netty初步详解)(六)
- 系统间通信方式之(Java之RMI初步使用详解)(八)
- 系统间的通信方式之(Java RMI方式详解下)(九)
- java网络编程之Netty实战数据通信(七)
- 系统间通信方式之(ActiveMQ的基础使用参数详解2)(十三)
- 分布式Java应用之基于消息方式实现系统间的通信(2)
- 通信算法之七:通信系统算法设计、码率,扩频,调制方式算法选择
- 一起学Netty(七)之 TCP粘包拆包基本解决方案
- 一起学Netty(七)之 TCP粘包拆包基本解决方案
- Netty初步之hello world
- Netty初步之hello world
- Netty初步之Hello World
- Netty初步之hello world
- Netty初步之Hello World
- Netty初步之Hello World
- 系统间通信方式之(ActiveMQ的使用性能优化之干柴烈火4)(十五)
- Android与服务器端通信方式(二)之Socket详解
- 【Oracle】delete表后commit后怎么找回,方法
- EditTextp篇
- 从三位数推广到四位数
- crontab 使用方法
- Eclipse中Cannot nest src folder解决方法
- 系统间通信方式之(Java之Netty初步详解)(七)
- git上传下载
- jupyter(ipython) notebook的 修改工作路径方法
- 你真的会用Retrofit2吗?Retrofit2完全教程
- SpringBoot学习问题笔记
- 期刊影响因子查询
- Spring的IoC容器实现
- 第14章 高级I/O函数
- python3 使用决策树进行分类