《Netty实战》读书笔记(二)
这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战
主要内容
- EnumbedChannel和单元测试
- 编码器和解码器
- SSL /TLS强化
- 心跳检测
- 其他扩展协议
测试入站信息
使用EmbeddedChannel测试ChannelHandler
自定一个解码器:FixedLengthFrameDecoder
public class FramLengthFrameDecoder extends ByteToMessageDecoder {
private final int frameLength;
public FramLengthFrameDecoder(int frameLength) {
this.frameLength = frameLength;
}
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
while(in.readableBytes() >= frameLength){
ByteBuf buf = in.readBytes(frameLength);
out.add(buf);
}
}
}
使用单元测试测试如下情况
public class FixedLengthFrameDecoderTest {
@Test
public void testFixedLengthFrameDecoder() {
ByteBuf buf = Unpooled.buffer();
for (int i = 0; i < 9; i++) {
buf.writeByte(i);
}
ByteBuf input = buf.duplicate();
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new FixedLengthFrameDecoder(3));
assertTrue(embeddedChannel.writeInbound(input.retain()));
// 标记为完成的状态
assertTrue(embeddedChannel.finish());
// 测试是否为3个划分的
assertEquals(buf.readSlice(3), embeddedChannel.readInbound());
assertEquals(buf.readSlice(3), embeddedChannel.readInbound());
assertEquals(buf.readSlice(3), embeddedChannel.readInbound());
assertNull(embeddedChannel.readInbound());
}
@Test
public void testFiexdLengthFrameDecoder2(){
ByteBuf buf = Unpooled.buffer();
for (int i = 0; i < 9; i++) {
buf.writeByte(i);
}
ByteBuf input = buf.duplicate();
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new FixedLengthFrameDecoder(3));
assertFalse(embeddedChannel.writeInbound(input.readBytes(2)));
}
}
测试出站信息
使用AbsIntegerEncoder
/**
* 自定义编码器
* @param ctx
* @param msg
* @param out
* @throws Exception
*/
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List out) throws Exception {
while(buf.readableBytes() > 4){
int abs = Math.abs(buf.readInt());
out.add(abs);
}
}
}
注意一个int 占4个Byte, 如果不够字节长度无法处理
启用单元测试
public class AbsIntegerEncoderTest {
@Test
public void testAbsIntegerEndocer(){
ByteBuf buffer = Unpooled.buffer();
for (int i = -1; i > -9; i--) {
buffer.writeInt(i);
}
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new AbsIntegerEncoder());
Assert.assertTrue(embeddedChannel.writeOutbound(buffer));
assertTrue(embeddedChannel.finish());
// 查看绝对值是否相等
for (int i = 1; i < 8; i++) {
assertEquals(i, embeddedChannel.readOutbound());
}
}
}
异常处理
我们限制了使用指定的规格去读取数据,如果超出了数据范围,我们需要舍弃并且抛出异常
改进之后的代码如下:
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// while(in.readableBytes() >= frameLength){
// ByteBuf buf = in.readBytes(frameLength);
// out.add(buf);
// }
int readableBytes = in.readableBytes();
if(readableBytes > frameLength){
in.clear();
throw new TooLongFrameException();
}
// 否则,读取对应的字节数据
ByteBuf byteBuf = in.readBytes(readableBytes);
out.add(byteBuf);
}
}
注意,这里使用抛出异常的方式来确定问题点
解码器
将字节信息解码为消息: ByteToMessageDecoder 和 ReplayingDecoder ;
将消息进行特殊转换:MessageToMessageDecoder 。
根据行尾的字符进行处理(如换行符):io.netty.handler.codec.LineBasedFrameDecoder
Http 解码器:io.netty.handler.codec.http.HttpObjectDecoder
编码器
消息编码为字节:MessageToByteEncoder
消息编码为消息:MessageToMessageEncoder
编解码复合
字节转为消息:ByteToMessageCodec
消息转消息:`MessageToMessageCodec``
特殊的解决重用问题:CombinedChannelDuplexHandler
Character转回字节:CharToByteEncoder
通过SSL/TLS 强化Netty
Netty的OpenSSL/SSLEngine实现
Java:javax.net.ssl
Netty在此基础上使用:名为SslHandler 的 ChannelHandler
会根据OpenSSL配置支持选择netty或者jdk 的实现
netty SSL 调用流程:
- 拦截入站数据
- 对数据解密,定向到入站端
- 通过
sslHandler传递出站数据- 数据进行加密,然后传出出站
Http完整处理流程
添加一个Http编解码器的示范代码
if (client) { ← -- 如果是客户端,则添加HttpResponseDecoder 以处理来自服务器的响应
pipeline.addLast("decoder", new HttpResponseDecoder());
pipeline.addLast("encoder", new HttpRequestEncoder()); ← -- 如果是客户端,则添加
HttpRequestEncoder以向服务器发送请求
} else {
pipeline.addLast("decoder", new HttpRequestDecoder()); ← -- 如 果 是 服 务 器 , 则 添 加
HttpRequestDecoder以接收来自客户端的请求
pipeline.addLast("encoder", new HttpResponseEncoder()); ← -- 如 果 是 服 务 器 , 则 添 加
HttpResponseEncoder以向客户端发送响应
}
压缩Http
客户端:HttpContentDecompressor
服务端:HttpContentCompressor
如果是jdk6版本以前,需要加入依赖
JZlib<dependency> <groupId>com.jcraft</groupId> <artifactId>jzlib</artifactId> <version>1.1.3</version> </dependency>
Websocket支持
核心handler:WebSocketServerProtocolHandler
注意:websocket是建立在Http协议的基础之上的,意味着不能直接使用websocket协议,必须事先经过升级
心跳检测:
心跳检测使用的频率比较高,这里给出一个代码案例:
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(0, 0, 5, TimeUnit.SECONDS));
pipeline.addLast(new HeartbeatHandler());
}
// ← -- 实现userEven t-Triggered()方法以发送心跳消息
public static final class HeartbeatHandler extends ChannelInboundHandlerAdapter {
// 发送到远程节点的心跳消息
private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(
Unpooled.copiedBuffer("HEARTBEAT", CharsetUtil.ISO_8859_1));
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
// 如果是心跳的事件
if(evt instanceof IdleStateEvent){
ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate())
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}else{
// 如果不是进行传递
super.userEventTriggered(ctx, evt);
}
}
}
public static void main(String[] args) throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class)
.group(eventLoopGroup)
.localAddress(new InetSocketAddress(8888))
.childHandler(new IdleStateHandlerInitializer());
serverBootstrap.bind().sync();
}
其他协议
基于分隔符的协议:
使用LineBasedFrameDecoder 进行分隔符的切割操作
他的扩展有下面几种:
Cmd
CmdDecoder
CmdHandler
CmdHandlerInitializer
ChannelInitializer
这五种对应的扩展
基于长度协议
LengthFieldBasedFrameDecoder
传输大型数据
ChunkedStream
序列化数据
jdk的序列化接口介绍:
- CompatibleObjectDecoder
- CompatibleObjectEncoder
- ObjectDecoder
- ObjectEncoder
基本满足了任何实现了序列化接口的序列化类
推荐的序列化API:
-
JBoss Marshalling比JDK快3倍,并且更为紧凑
-
Protocol Buffers谷歌开源的支持多语言的序列化API