Netty 为许多通用协议提供了编解码器和处理器,几乎能够开箱即用, 这减小了你在那些至关繁琐的事务上原本会花费的时间与精力。另外,这篇文章中,就不涉及 Netty 对 WebSocket协议 的支持了,由于涉及的篇幅有点大,会在下一篇文章作一个具体的介绍。java
SSL 协议是安全协议,层叠在其余协议之上。为了支持 SSL/TLS, Java 提供了 javax.net.ssl 包,它的 SSLContext 和 SSLEngine 类使得实现解密和加密至关简单直接。 Netty 经过一个名为 SslHandler 的 ChannelHandler 实现利用了这个 API, 其中 SslHandler 在内部使用 SSLEngine 来完成实际的工做。下图描述的是 SslHandler 的数据流。git
@Override protected void initChannel(Channel ch) throws Exception { ByteBufAllocator byteBufAllocator = ch.alloc(); //对于每一个 SslHandler 实例,都使用 Channel 的 ByteBufAllocator 从 SslContext 获取一个新的 SSLEngine SSLEngine sslEngine = context.newEngine(byteBufAllocator); //服务器端模式,客户端模式设置为true sslEngine.setUseClientMode(false); //不须要验证客户端,客户端不设置该项 sslEngine.setNeedClientAuth(false); //要将 SslHandler 设置为第一个 ChannelHandler。这确保了只有在全部其余的 ChannelHandler 将他们的逻辑应用到数据以后,才会进行加密。 //startTls 若是为true,第一个写入的消息将不会被加密(客户端应该设置为true) ch.pipeline().addFirst("ssl",new SslHandler(sslEngine, startTls)); }
tips:对于 ChannelPipeline 链中 ChannelHandler 执行的顺序 —— 入站事件顺序执行、出站事件逆序执行。github
HTTP 是基于请求/响应模式的:客户端向服务器发送一个 HTTP 请求,而后服务器将会返回一个 HTTP 响应。 下图展现了 Netty 中 HTTP请求和响应的组成部分:安全
Netty 对 HTTP 协议的支持主要提供了如下 ChannelHandler:服务器
HttpResponseDecoder:解码器,用于客户端,解码来自服务端的响应。
HttpRequestEncoder:编码器,用户客户端,编码向服务端发送的请求。
HttpRequestDecoder:解码器,用于服务端,解码来自客户端的请求。
HttpResponseEncoder:编码器,用于服务端,编码向客户端的响应。
HttpClientCodec:编解码器,用户客户端,效果等于 HttpResponseDecoder + HttpRequestEncoder。
HttpServerCodec:编解码器,用户服务端,效果等于 HttpRequestDecoder + HttpResponseEncoder。
HttpObjectAggregator:聚合器,因为 HTTP 的请求和响应可能由许多部分组成,须要聚合它们以造成完整的消息,HttpObjectAggregator 能够将多个消息部分合并为 FullHttpRequest 或者 FullHttpResponse 消息。
HttpContentCompressor:压缩,用户服务端,压缩要传输的数据,支持 gzip 和 deflate 压缩格式。
HttpContentDecompressor:解压缩,用于客户端,解压缩服务端传输的数据。网络
@Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); SSLEngine sslEngine = sslContext.newEngine(ch.alloc()); if (isClient) { //使用 HTTPS,添加 SSL 认证 pipeline.addFirst("ssl", new SslHandler(sslEngine, true)); pipeline.addLast("codec", new HttpClientCodec()); //一、建议开启压缩功能以尽量多地减小传输数据的大小 //二、客户端处理来自服务器的压缩内容 pipeline.addLast("decompressor", new HttpContentDecompressor()); }else { pipeline.addFirst("ssl", new SslHandler(sslEngine)); //HttpServerCodec:将HTTP客户端请求转成HttpRequest对象,将HttpResponse对象编码成HTTP响应发送给客户端。 pipeline.addLast("codec", new HttpServerCodec()); //服务端,压缩数据 pipeline.addLast("compressor", new HttpContentCompressor()); } //目的多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse //将最大的消息为 512KB 的HttpObjectAggregator 添加到 ChannelPipeline //在消息大于这个以后会抛出一个 TooLongFrameException 异常。 pipeline.addLast("aggregator", new HttpObjectAggregator(512 * 1024)); }
tips:当使用 HTTP 时,建议开启压缩功能以尽量多地减少传输数据的大小。虽然压缩会带来一些 CPU 时钟周期上的开销。框架
TCP 传输过程当中,客户端发送了两个数据包,而服务端却只收到一个数据包,客户端的两个数据包粘连在一块儿,称为粘包;异步
TCP 传输过程当中,客户端发送了两个数据包,服务端虽然收到了两个数据包,可是两个数据包都是不完整的,或多了数据,或少了数据,称为拆包;ide
发生TCP粘包、拆包主要是因为下面一些缘由:ui
一、应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。
二、应用程序写入数据小于套接字缓冲区大小,网卡将应用屡次写入的数据发送到网络上,这将会发生粘包。
三、进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包。
四、接收方法不及时读取套接字缓冲区数据,这将发生粘包。
Netty 预约义了一些解码器用于解决粘包和拆包现象,其中大致分为两类:
基于分隔符的协议:在数据包之间使用定义的字符来标记消息或者消息段的开头或者结尾。这样,接收端经过这个字符就能够将不一样的数据包拆分开。
基于长度的协议:发送端给每一个数据包添加包头部,头部中应该至少包含数据包的长度,这样接收端在接收到数据后,经过读取包头部的长度字段,便知道每个数据包的实际长度了。
public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast( // 将提取到的桢转发给下一个Channelhandler new LineBasedFrameDecoder(64 * 1024), // 添加 FrameHandler 以接收帧 new FrameHandler() ); } public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { //Do something with the data extracted from the frame } } }
LengthFieldBasedFrameDecoder 是 Netty 基于长度协议解决拆包粘包问题的一个重要的类,主要结构就是 header+body 结构。咱们只须要传入正确的参数就能够发送和接收正确的数据,那吗重点就在于这几个参数的意义。下面咱们就具体了解一下这几个参数的意义。先来看一下LengthFieldBasedFrameDecoder主要的构造方法:
public LengthFieldBasedFrameDecoder( int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip)
maxFrameLength:最大帧长度。也就是能够接收的数据的最大长度。若是超过,这次数据会被丢弃。
lengthFieldOffset:长度域偏移。就是说数据开始的几个字节可能不是表示数据长度,须要后移几个字节才是长度域。
lengthFieldLength:长度域字节数。用几个字节来表示数据长度。
lengthAdjustment:数据长度修正。由于长度域指定的长度可使 header+body 的整个长度,也能够只是body的长度。若是表示header+body的整个长度,那么咱们须要修正数据长度。
initialBytesToStrip:跳过的字节数。若是你须要接收 header+body 的全部数据,此值就是0,若是你只想接收body数据,那么须要跳过header所占用的字节数。
public class LengthBasedInitializer extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast( new LengthFieldBasedFrameDecoder(64 * 1024, 0, 8), new FrameHandler() ); } public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { //处理桢的数据 } } }
tips:UDP协议不会发生沾包或拆包现象, 由于UDP是基于报文发送的,在UDP首部采用了16bit来指示UDP数据报文的长度,所以在应用层能很好的将不一样的数据报文区分开。
因为网络饱和的可能性,如何在异步框架中高效地写大块的数据是一个特殊的问题。Netty 经过一个 FileRegion 接口来实现,其在 Netty 的API 文档中的定义是:"经过支持零拷贝的文件传输的 Channel 来发送的文件区域"。可是该接口只适用于文件内容的直接传输,不包括应用程序对文件数据的任何处理。
若是大块的数据要从文件系统复制到用户内存中时,能够安装一个 ChunkedWriteHandler,并用 ChunkedInput 实现写入文件数据。 它支持异步写大型数据流,而又不会致使大量的内存消耗。
public class ChunkedWriteHandlerInitializer extends ChannelInitializer<Channel> { private final File file; private final SslContext sslCtx; public ChunkedWriteHandlerInitializer(File file, SslContext sslCtx) { this.file = file; this.sslCtx = sslCtx; } @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast( new SslHandler(sslCtx.newEngine(ch.alloc())), // 添加 ChunkedWriteHandler 以处理做为 ChunkedInput 传入的数据 new ChunkedWriteHandler(), new WriteStreamHandler() ); } private final class WriteStreamHandler extends ChannelHandlerAdapter { //当链接创建时,channelActive() 方法将使用 ChunkedInput 写文件数据 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); ctx.writeAndFlush(new ChunkedStream(new FileInputStream(file))); } } }
Netty提供的用于和JDK进行互操做的序列化类 :
Netty提供的用于和 JBoss Marshalling 进行互操做的序列化类 :
public class MarshallingInitializer extends ChannelInitializer<Channel> { private final MarshallerProvider marshallerProvider; private final UnmarshallerProvider unmarshallerProvider; public MarshallingInitializer(MarshallerProvider marshallerProvider, UnmarshallerProvider unmarshallerProvider) { this.marshallerProvider = marshallerProvider; this.unmarshallerProvider = unmarshallerProvider; } @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast( new MarshallingDecoder(unmarshallerProvider), new MarshallingEncoder(marshallerProvider), new ObjectHandler() ); } public static final class ObjectHandler extends SimpleChannelInboundHandler<Serializable> { @Override protected void messageReceived(ChannelHandlerContext ctx, Serializable msg) throws Exception { } } }
Netty提供的用于和 Protocol Buffers 进行互操做的序列化类 :
参考资料:《Netty IN ACTION》
演示源代码:https://github.com/JMCuixy/NettyDemo/tree/master/src/main/java/org/netty/demo/protocol