最近有个项目要用到netty,对于netty进行了研究,简单的总结一下。
学习netty的意义
学习网络编程,怎样编写高性能的程序是一件十分需要功底的事情,需要明白网络协议层面的TCP/IP协议,需要了解操作系统对网络IO的知识,相对来说非常复杂。
JAVA提供了BIO、NIO、AIO等网络编程接口,用于编写高性能的网络通讯程序,但是由于JAVA本身提供的接口要考虑面向通用,要正确编写商用级别的网络通讯非常困难。因此Netty和MINA出现了,本文更偏向于Netty,因为从应用上来看,Netty应用的范围更加广泛。
netty使用
netty比较好的书是李林峰写的《netty权威指南》,这本书比<netty in action>要更加适合学习。
netty定义要看netty主页
“Netty is a NIO client server framework which enables quick and
easy development of network applications such as protocol servers
and clients. It greatly simplifies and streamlines network
programming such as TCP and UDP socket server.”
按照定义,netty是一种框架,netty框架如下图
- netty是典型的层架构,其中下层管理连接、线程和高性能读写,是netty高性能设计的关键。
- PipeLine层是用职责链模式,管理netty的编解码。
- 编解码应用层。基于PipleLine,提供了业务编码能力,也就是各种编解码器,如图支持http、ftp、thrift protocol等。
在这些层之上,应用者可以将注意力集中于业务,而不必关注与底层通讯。
简单的实现
通过框架和示例代码,可以了解到构建一个netty程序还是非常简单的,如下netty主页上server的guide代码:
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (7)
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
netty是基于NIO构建的,可以看到nettty的关键概念还是channel\selector,netty对此进行了封装,几十行代码很容易的完成了服务端框架。
ServerBootStrap为启动类,socket关键的配置、eventloop、hannel的关联都通过bootstrap类来完成。同时netty自己封装了future,完成netty自身的多线程功能。
netty参数
netty参数可以参见ChannelOptional类,为何要封装这样一个类,是因为netty参数太多,如果不封装,让程序员自己来写,将很难配置或者配置正确。
百度上有很多文章介绍netty参数。我们最常用配置的参数是
参数 | 含义 |
---|---|
keepalive | 启用禁用Nagle算法 |
CONNECT_TIMEOUT_MILLIS | 连接超时时间 |
SO_BACKLOG | 服务端连接队列的长度 |
参数设置很多,可以百度一下或者看netty官方文档。
netty粘包和拆包
netty的底层通讯问题BootStrap已经封装好了,接着最主要的问题粘包和拆包,netty怎么解决的呢?
首先,需要先了解粘包和拆包的概念。socket收到信息,如果是多个包,那么就是粘包,如果其中有个包只有一部分,就是拆包。
UDP包报文中包括了长度,是面向报文的,发送几次,接受几次,不存在粘包和拆包的问题。而TCP协议,可以启用Nagle算法优化,是面向数据流的,并不知道报文的起始点,因此有粘包和拆包。
粘包和拆包有三种解决办法:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
2和3比较容易理解,其中1是我此次项目用到的重点,也是netty中处理粘包和拆包比较精彩的地方。当然首先你需要具备netty关于hendler的知识。
对于1,也就是说需要用私有的协议,需要使用到LengthFieldBasedFrameDecoder解码,其中有四个参数,理解了也就掌握了使用。
参数 | 含义 |
---|---|
lenthFieldOffset | offset |
lenthFieldLength | 长度,一般指body的长度 |
lenthAdjustment | 调整长度,用于长度字节后有其它字节 |
initialBytesToStrip | 报文去掉的头部长度 |
lenthFieldOffset | lenthFiledLength | lenthAdjustment | body |
---|
如上是一个私有报文,各个参数分别对应如上,对于LengthFieldBasedFrameDecoder的前三个参数很容易理解,initialBytesToStrip参数代表什么,如果设置为0,在处理read方法的参数msg的时候,msg就是整个报文长度,如果只想获得body的长度,就把initialBytesToStrip设置为前三个参数相加。
从这里可以看到,netty已经帮应用者处理好了拆包粘包各种事宜,接着应用者只需要关注应用即可。
总结
懂得了netty,就可以很容易的把应用者从协议底层解放出来,快速构建商用级系统,反之,就可能陷入协议底层,系统不容易稳定。但是对于技术人员本身来说,只懂得netty,是不够的,一定需要深入到协议本身去理解网络编程技术。举个例子来说,如果不用java,你该怎么办呢,所以还是要深入掌握底层知识,如redis,用c自己实现了nio进行网络通讯。
网友评论