一TCP 粘包/拆包
Tcp是个”流“协议,所谓流,就是没有界限的一串数据。
一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题
二、粘包问题的解决策略
(1)消息定长。报文大小固定长度,不够空格补全。发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。
(2)包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。
(3)将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段
(4)其他应用层协议
三 Netty提供的半包解码器解决TCP粘包/拆包问题
1 利用LineBaseFrameDecoder解决TCP粘包问题
LineBaseFrameDecoder工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有"\n"或者"\r\n",如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标识的解码器,支持携带结束符,或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就抛出异常,同时忽略掉之前读到的异常码流。
2 StringDecoder
StringDecoder的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的handler.
LineBaseFrameDecoder+StringDecoder组合是按行切换的文本解码器,它被设计用来支持TCP的粘包和拆包。
四 实列
1 服务端
public static void main(String[] arg) throws InterruptedException
{
//定义两个事件组
//接收连接但是不对连接做任何处理会把事情交给workerGroup去做
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//启动服务
ServerBootstrap serverBootStrap = new ServerBootstrap();
serverBootStrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
//绑定端口
ChannelFuture f = serverBootStrap.bind(8080).sync();
//等待服务端 监听端口关闭
f.channel().closeFuture().sync();
}catch (Exception ex)
{
}
finally {
//优雅的关闭Netty
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class ChildChannelHandler extends ChannelInitializer<SocketChannel>
{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// LineBasedFrameDecoder 添加行解析器,添加stringDecoder
//StringDecoder作用就是将接收到的消息转为字符串
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast("TestHttpServerHandler",new TestHttpServerHandler());
}
}
2 客户端
public static void main(String[] arg)
{
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// LineBasedFrameDecoder 添加行解析器,添加stringDecoder
//StringDecoder作用就是将接收到的消息转为字符串
pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new LoggingHandler(LogLevel.INFO))
.addLast(new TimeClientHandler());
}
});
//start the client
ChannelFuture future = bootstrap.connect("127.0.0.1", 8080).sync();
//wait until the connection is closed
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//shut down the event loop to terminate all threads
group.shutdownGracefully();
}
}