前言
在博客的 _drafts
里看到了一篇 2017-02-17-我的java存在深入浅出.md
,那个时候在完成了一定量的编码之后,感觉还是想提高自己,于是就开始了阅读。可能以为阅读完几本书就够了,什么 JVM,并发,编程宝典之类的。现在看起来一年多前的自己还是比较年轻的,即便现在也是很年轻。
今天偶然看到了 akka 这么一个很厉害的高并发分布式框架,于是想学习一下,发现他和 netty 有千丝万缕的联系。Akka 针对 IO 操作有一个抽象,这和 netty 是一样的。使用 Akka 可以用来创建计算集群,Actor 在不同的机器之间传递消息。从这个角度来看,Akka 相对于 Netty 来说,是一个更高层次的抽象。但 akka 用 Scala 写的,不太熟,估计会看晕。
然而,发现自己的 netty 属于半知半解的状态,没看过源码,没了解架构,没写过深层的代码,所以这篇博客才来了。
学习过程
学习过程主要围绕 gitbookEssential Netty in Action 《Netty 实战(精髓)》来进行。下面仅仅是记录一些笔记,方便记忆和回忆。同时提供一些总结性的东西,或者提供一些原书没有的东西。
1. echo server 的编码
整体流程
- 写一个服务端
- 写一个客户端
- 启动服务端,客户端进行调用
服务端任务
- 创建 ServerBootstrap 实例来引导服务器并随后绑定
- 创建并分配一个 NioEventLoopGroup 实例来处理事件的处理,如接受新的连接和读/写数据。
- 指定本地 InetSocketAddress 给服务器绑定
- 通过 EchoServerHandler 实例给每一个新的 Channel 初始化
- 最后调用 ServerBootstrap.bind() 绑定服务器
客户端任务
- 连接服务器
- 发送信息
- 发送的每个信息,等待和接收从服务器返回的同样的信息
- 关闭连接
具体来说就是
- 一个 Bootstrap 被创建来初始化客户端
- 一个 NioEventLoopGroup 实例被分配给处理该事件的处理,这包括创建新的连接和处理入站和出站数据
- 一个 InetSocketAddress 为连接到服务器而创建
- 一个 EchoClientHandler 将被安装在 pipeline 当连接完成时
- 之后 Bootstrap.connect()被调用连接到远程的 - 本例就是 echo(回声)服务器。
代码放在 https://github.com/xjtushilei/netty-demo/tree/master/src/main/java/echo,还是比较好理解的。首先写一个 netty 的基本通讯流程来理解过程,然后下面的章节来理解 为什么
。
2. 主要概念学习
- BOOTSTRAP
- CHANNEL
- CHANNELHANDLER
- CHANNELPIPELINE
- EVENTLOOP
- CHANNELFUTURE
详细介绍简书传送门,有图片加深理解与记忆。
下面将其他的一些核心的组件。
Buffer API
主要包括
- ByteBuf
- ByteBufHolder
Netty 使用 reference-counting
(引用计数)来判断何时可以释放 ByteBuf 或 ByteBufHolder 和其他相关资源,从而可以利用池和其他技巧来提高性能和降低内存的消耗。这一点上不需要开发人员做任何事情,但是在开发 Netty 应用程序时,尤其是使用 ByteBuf 和 ByteBufHolder 时,你应该尽可能早地释放池资源。 Netty 缓冲 API 提供了几个优势:
- 可以自定义缓冲类型
- 通过一个内置的复合缓冲类型实现零拷贝
- 扩展性好,比如 StringBuilder
- 不需要调用 flip() 来切换读/写模式
- 读取和写入索引分开
- 方法链
- 引用计数
- Pooling(池)
Channel
Channel 这一章主要有,Channel 中大量用了 buffer API,所以刚才了解 buffer 很重要。虽然有些细节仅仅是知其意而看不懂实现。
- Channel
- ChannelHandler
- ChannePipeline
- ChannelHandlerContext
codec
- 编码器
- 解码器
- 编解码器
第一次接触到 TCP 黏包、拆包的问题。针对该问题主要有以下方法:
- (1)消息长度固定:累计读取到固定长度为 LENGTH 之后就认为读取到了一个完整的消息。然后将计数器复位,重新开始读下一个数据报文。
- (2)将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符。
- (3)通过在消息头中定义长度字段来标示消息的总长度。
我们代码里采用的 DelimiterBasedFrameDecoder 就是(3)里面的。以 lineDelimiter,即换行符(linux 和 windows 的换行符都可以)。关于这部分内容,我想专门开一篇博客来进行讲解:Netty 之 TCP 黏包与拆包
SSL
关于 ssl 这块,netty 基于 ssl 的 handler 来实现的,并提供了默认的实现。但是我个人不推荐在 netty 层实现 ssl。当然了实现没问题,但是不推荐这么用。我浅显的认为,https 等直接在网关层进行处理即可,不需要再应用层进行 ssl 的处理。一旦连接从网关进入了内网,就是 http 进行传输了。这样应用层不用关系加解密逻辑,只需要关心业务逻辑。
netty 还提供了 http 的 encoder 和 decoder:
- client: 添加 HttpResponseDecoder 用于处理来自 server 响应
- client: 添加 HttpRequestEncoder 用于发送请求到 server
- server: 添加 HttpRequestDecoder 用于接收来自 client 的请求
- server: 添加 HttpResponseEncoder 用来发送响应给 client
WebSocket
HTTP 是不错的协议,但是如果需要实时发布信息怎么做?有个做法就是客户端一直轮询请求服务器,这种方式虽然可以达到目的,但是其缺点很多,也不是优秀的解决方案,为了解决这个问题,便出现了 WebSocket。
WebSocket 允许数据双向传输,而不需要请求-响应模式。早期的 WebSocket 只能发送文本数据,然后现在不仅可以发送文本数据,也可以发送二进制数据,这使得可以使用 WebSocket 构建你想要的程序。下图是 WebSocket 的通信示例图:
- Client (HTTP) 与 Server 通讯
- Server (HTTP) 与 Client 通讯
- Client 通过 HTTP(s) 来进行 WebSocket 握手,并等待确认
- 连接协议升级至 WebSocket
3. 简单多人聊天室
然后参考代码,整了一个 simple 聊天,感觉用 netty 写一个聊天室感觉很有意思。
主要就是通过记录所有连接到服务器的 channel,维护了这样的一个 channel 的 list,每当有新用户连接到服务器或者关闭库客户端,都动态的更新 channel list。每当接收到一个 channel 的消息后,触发 handler,转发消息给所有其他在线的 handler。
效果图:
代码见:https://github.com/xjtushilei/netty-demo/tree/master/src/main/java/simplechart
其中需要注意的几个解释如下:
sync()
同步等待SO_BACKLOG
服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定了队列的大小SO_KEEPALIVE
参数对应于套接字选项中的 SO_KEEPALIVE,该参数用于设置 TCP 连接,当设置该选项以后,连接会测试链接的状态,这个选项用于可能长时间没有数据交流的连接。当设置该选项以后,如果在两小时内没有数据的通信时,TCP 会自动发送一个活动探测数据报文。
其他的 ChannelOption
参见:Netty 之 ChannelOption
NioServerSocketChannel.class
我们最常用的就是 socket,除了 tcp 外,还有 sctp 协议的 channel。
接下来
最近 3 天的零散时间,把 netty 基本的东西都了解了下,缺少实践。有以下几点可考虑,用 netty 写简单 web 框架,用 netty 写简单 rpc 框架。有打算,很难付诸行动。
接下来准备看点实习中可能会用到的的吧,如果有机会还能跟大家一起分享。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于