NIO和Netty全文
Handler链调用机制
ChannelHandler
处理入站和出站数据的逻辑容器
ChannelInboundHandler
处理入站数据,读取,从头到尾ChannelOutboundHandler
处理出站数据,发送,从尾到头
- 入站数据被解码
inbound
,出站数据被编码outbound
,根据添加的顺序独立运行,通过inbound/outbound
来判断是否执行该handler
ProtobufEncoder
编码器
ProtobufDecoder
解码器
ctx
调用writeAndFlush
交给当前handler的下一个outHandler
;ctx.channel.writeAndFlush
从pipeline
中最后一个outHandler
开始调用
解码器
ByteToMessageDecoder
继承关系,对入站数据进行缓冲,直到数据准备好被处理
,读取全部信息
- 由于不知道远程节点是否会一次性发送一个完整的信息,tcp可能会出现
沾包拆包
的问题 - 解码
int
的例子
自定义编解码器
- 双向传递long类型数据
LongToByteEncoder
public class LongToByteEncoder extends MessageToByteEncoder<Long> {
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("LongToByteEncoder - encode");
System.out.println("msg: " + msg);
out.writeLong(msg);
}
}
复制代码
ByteToLongDecoder
public class ByteToLongDecoder extends ByteToMessageDecoder {
/**
* decode会根据接受的数据,被调用多次,知道没有新的元素被添加到list,或者buf没有更多可读的字节
* 如果out不为空,就会将list的内容传递给下一个inboundhandler,那么该处理器的方法也会被调用多次
* @param ctx 上下文
* @param in 入站buf
* @param out list集合,将解码后的数据传递给下一个handler
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//long 8个字节,至少有8个字节才能读取
System.out.println("ByteToLongDecoder - decode");
//满8个字节读取一次
if (in.readableBytes() >= 8) {
out.add(in.readLong());
}
}
}
复制代码
ClientInitializer
public class ClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//出站handler,进行编码
ch.pipeline().addLast(new LongToByteEncoder());
//解码器,inbound
ch.pipeline().addLast(new ByteToLongDecoder());
//业务handler
ch.pipeline().addLast(new ClientHandler());
}
}
复制代码
ServerInitializer
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//入站handler
pipeline.addLast(new ByteToLongDecoder());
//出站handler
pipeline.addLast(new LongToByteEncoder());
pipeline.addLast(new ServerHandler());
}
}
复制代码
public class ClientHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("server: " + ctx.channel().remoteAddress() + " long: "+ msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("ClientHandler - channelActive");
ctx.writeAndFlush(12345L);
//该处理器的前一个处理器为 `LongToByteEncoder`,只能处理long类型的数据
//ctx.writeAndFlush(Unpooled.copiedBuffer("abcdabcdabcdabce", StandardCharsets.UTF_8));
}
}
复制代码
- 当写String时,服务器收到的结果,为分开两次读
ByteToLongDecoder - decode
[12345]
client: /127.0.0.1:64208 long: 12345
ByteToLongDecoder - decode
[7017280452178371428] abcdabcd
client: /127.0.0.1:64208 long: 7017280452178371428
ByteToLongDecoder - decode
[7017280452178371429] abcdabce
client: /127.0.0.1:64208 long: 7017280452178371429
复制代码
ServerHandler
public class ServerHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("client: " + ctx.channel().remoteAddress() + " long: " + msg);
//发送long
ctx.writeAndFlush(98773L);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
复制代码
解析
MessageToByteEncoder
中write
方法,判断该类型是否能处理,如果不能处理,直接写出去ctx.write(msg, promise);
,符合责任链模式
- 调用本地方法
isInstance
- 编码器和解码器,接受消息类型与待处理消息类型不一致,就不会执行该handler
- 解码器进行解码,需要判断
buf
中的数据是否符合数据类型,否则接收到的结果与期望不一致
ReplayingDecoder
- 继承
MessageToByteDecoder
,不需要判断readablelBytes() 是否有可读的区域
,数据是否足够读取,内部进行管理 - 参数类型用
Void
public class ReplayByteToLongDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("ReplayByteToLongDecoder - decode");
//数据量不足,会直接跳过,不传给下一个handler
out.add(in.readLong());
}
}
复制代码
- 并不是所有
ByteBuf
的操作都支持,调用不支持的,会报异常 - 在某些情况下,慢于
MessageToByteDecoder
,网络缓慢并且消息格式负责,消息会被拆成多个碎片
其他编解码器
LineBasedFrameDecoder
用行尾控制字符\n或者\r\n
,作为分隔符来解析数据DelimiteBasedFrameDecoder
自定义的特殊字符作为消息的分隔符HttpObjectDecoder
解析http数据LengthFieldBasedFrameDecoder
指定长度来标识整包消息,可以自动地处理黏包和半包消息
TCP粘包和拆包
- TCP面向连接,面向流,收发两端都要有
一一成对的socket
,发送段为了将多个发给接收端的包,更有效的发给对方,使用了优化算法 Nagle
,将多次间隔较小且数据量小的数据,合并成一个数据块,进行封包
提高了效率,但是接收端难分辨出完整的数据包,面向流的通信
无消息保护边界
- 客户端分别发送了两个数据包
D1和D2
给服务端,服务端一次读取到的字节数是不确定的,有四种情况
- 服务端两次读取到了两个独立的数据包
- 服务端一次接收到了两个数据包,D1和D2粘合在一起,粘包
- 服务端分两次读取到了数据包,第一次读取到了
完整的D1包和部分的D2包
,第二次读取到了D2包的剩余内容
,拆包- 服务端分两次读取到了数据包,第一次读取到D1包的部分内容,第二次读取到
部分的D1包和完整的D2包
解决粘包和拆包
- 自定义协议+编解码器,解决服务端每次读取数据长度
协议包类
//协议包
public class MessageProtocol {
private int len;
private byte[] content;
public int getLen() {
return len;
}
public void setLen(int len) {
this.len = len;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
public MessageProtocol(int len, byte[] content) {
this.len = len;
this.content = content;
}
}
复制代码
解码器
public class MessageDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MessageDecoder - decode");
int len = in.readInt(); //先读长度
byte[] content = new byte[len];
in.readBytes(content); //在读内容
MessageProtocol mp = new MessageProtocol(len, content);
out.add(mp);
}
}
复制代码
编码器
public class MessageEncoder extends MessageToByteEncoder<MessageProtocol> {
@Override
protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
System.out.println("MessageEncoder - encode");
out.writeInt(msg.getLen());
out.writeBytes(msg.getContent());
}
}
复制代码
ClientHandler
public class ClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//发送数据
for (int i = 0; i < 10; i++) {
String message = "hello server - " + i;
byte[] content = message.getBytes(StandardCharsets.UTF_8);
int len = content.length;
MessageProtocol mp = new MessageProtocol(len, content);
ctx.writeAndFlush(mp);
}
}
private int count = 0;
@Override
protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
int len = msg.getLen();
byte[] content = msg.getContent();
System.out.println("client len: " + len + " content: " + new String(content, StandardCharsets.UTF_8));
System.out.println("client receive count: " + count++);
}
复制代码
ServerHandler
public class ServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
private int count = 0;
@Override
protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
int len = msg.getLen();
byte[] content = msg.getContent();
System.out.println("server: len - " + len + " - " + new String(content, StandardCharsets.UTF_8));
System.out.println("receive count: " + count++);
//会写
String response = UUID.randomUUID().toString();
int resLen = response.getBytes(StandardCharsets.UTF_8).length;
MessageProtocol resMp = new MessageProtocol(resLen, response.getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(resMp);
}
复制代码