文章目录
Netty 解码器
为什么会有粘包拆包?
对于“套接字”来说,并非是我们调用 writer API 便将报文发送给接收方,而是系统底层根据一定的算法,对消息的大小进行拆解或拼装。这也是发送拆包粘包的最大原因。总体来讲,发生 TCP 粘包、拆包主要是由于下面一些原因:
1.应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。
2.应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。
3.进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包。
4.接收方法不及时读取套接字缓冲区数据,这将发生粘包。
解决方法
TCP本身是面向流的,作为网络服务器,如何从这源源不断涌来的数据流中拆分出或者合并出有意义的信息呢?通常会有以下一些常用的方法:
1.发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2.发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3.可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
Netty粘包拆包四大解码器
- FixedLengthFrameDecoder:定长解码器,按固定长度进行消息的读取;
- LineBasedFrameDecoder:行解码器,按行(\r\n)进行消息的读取;
- DelimiterBasedFrameDecoder:分隔符解码器,按照特殊的分隔符作为消息分隔符进行消息的读取;
- LengthFieldBasedFrameDecoder:自定义长度解码器,通过在消息头中定义消息长度字段来标志消息体的长度,然后根据消息的总长度来读取消息;
解码器使用
FixedLengthFrameDecoder
使用定长解码器 FixedLengthFrameDecoder,服务器(客户端)在接收消息后将自行判断该消息长度是否达到规定的帧数,每一帧一个字节,如果达到才会回调 channelRead 函数。否则会阻塞直到消息的长度达到规定的帧数。从而达到粘包拆包的目的。
package org.skystep.tcpserv;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//定长处理器 这里设置为8帧 每次接收数据会判断是否达到该长度,如果达到就回调 channelRead 函数;否则阻塞住;
pipeline.addLast(new FixedLengthFrameDecoder(8));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new ServerHandler());
}
}
如以上事例代码,如果在服务器的处理链上增加 FixedLengthFrameDecoder 解码器并且设置长度为8字节。那么客户端发送"1234567"长度为7的字符串,服务器不会进行读操作,直到下次服务再发送消息,凑足8个字节。
服务端实现
package org.skystep.tcpserv;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup wokerGroup = new NioEventLoopGroup();
try {
//用于启动NIO服务
ServerBootstrap serverBootstrap = new ServerBootstrap();
//通过工厂方法设计模式实例化一个channel
serverBootstrap.group(bossGroup, wokerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ServerInitializer());
//绑定服务器,该实例将提供有关IO操作的结果或状态的信息
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
System.out.println("在" + channelFuture.channel().localAddress() + "上开启监听");
//阻塞操作,closeFuture()开启了一个channel的监听器(这期间channel在进行各项工作),直到链路断开
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
bossGroup.shutdownGracefully();
wokerGroup.shutdownGracefully();
}
}
}
package org.skystep.tcpserv;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//定长处理器 这里设置为8帧 每次接收数据会判断是否达到该长度,如果达到就回调 channelRead 函数;否则阻塞住;
pipeline.addLast(new FixedLengthFrameDecoder(8));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new ServerHandler());
}
}
package org.skystep.tcpserv;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//读取消息,并按照自定义的协议格式进行消息的处理
String in = (String) msg;
System.out.println("收到的原始报文: " + in);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//出现异常的时候执行的动作(打印并关闭通道)
cause.printStackTrace();
ctx.close();
}
}
客户端实现
package org.skystep.tcpclient;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ClientInitializer());
ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
package org.skystep.tcpclient;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil