java面试题网站:www.javaoffers.com
引言:
tcp: 传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议.
(百度百科), 因为tcp是面向(字节)流的协议,所以就好像河流一样没有分割界限.所以tcp协议对要发送消息是不做分割的(即
只是顺序发送字节数据流,但是并不保证会把我们的消息整体发过去,有可能先发前部分然后再发后半部分(拆包),也有可能
会把两条我们要发送的消息合并进行发送(粘包)).
粘包/拆包
产生粘包/拆包的原因
- 写入的字节长度大于socket缓冲区的大小(通常产生拆包)
- 写入的字节长度小于socket缓冲去的大小(通常产生粘包)
- 进行 mss大小的tcp分段(最大报文段长度(MSS)是TCP协议的一个选项,用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度(不包括文段头))
- 以太网的playload 大于 MTU(最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小) 进行 ip分片(如果IP层有一个数据要传,且数据的长度比链路层的 MTU还大,那么IP层就要进行分片(fragmentation),把数据报分成若干片,这样每一个分片都小于MTU)
业界常用解决方法
- 消息指定长度,字节流读取到一定的长度时,把读取的字节流作为一个消息.然后重置消息长度为0,继续读取下一个消息.
- 消息指定结尾符号,字节流读取到指定的结尾符号时,把读取的字节作为一个消息.
netty解决方式
- LineBasedFrameDecoder: 以(linux下)\n作为结尾符号, 不同的平台可以System.getProperty(“line.separator”);查看, 该解码器也是System.getProperty(“line.separator”);作为结尾符号
- DelimiterBasedFrameDecoder: 可以自定义结尾分割符号.
- FixedLengthFrameDecoder: 读取固定字节流长度
Demo 以LineBasedFrameDecoder为例
Server
package com.mh.others.nio.netty.linebased.server;
import com.mh.others.log.LOGUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import java.io.File;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Description: netty 之 LineBasedFrameDecoder 解决拆包粘包
* @Auther: create by cmj on 2020/9/20 13:56
*/
public class LineBasedServer {
public static final AtomicInteger ai = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup work = new NioEventLoopGroup();
try {
ServerBootstrap lineBaseServer = new ServerBootstrap();
lineBaseServer.group(boss,work)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,100)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//添加处理链
pipeline.addLast(new LineBasedFrameDecoder(1024)) //指定一个行分割解码器,最大长度是1024Byte(解决拆包/粘包)
.addLast(new StringDecoder())//指定一个String解码器.将信息解析为String对象
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String msgStr = (String) msg; //因为上面指定了StringDecoder
LOGUtils.printLog(msgStr);
String separator = System.getProperty("line.separator");
ByteBuf byteBuf = Unpooled.copiedBuffer((ai.incrementAndGet() + separator).getBytes());
ctx.writeAndFlush(byteBuf);
}
});
}
});
//同步等待链接成功
ChannelFuture sync = lineBaseServer.bind(8080).sync();
//同步等待服务器端口关闭
sync.channel().closeFuture().sync();
}finally {
//关闭资源
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
}
Client
package com.mh.others.nio.netty.linebased.client;
import com.mh.others.log.LOGUtils;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.channel.unix.Socket;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Description: 模拟客户端
* @Auther: create by cmj on 2020/9/20 16:09
*/
public class LineBasedClient {
public AtomicInteger ai = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup clientWork = new NioEventLoopGroup();
try {
Bootstrap client = new Bootstrap();
client.group(clientWork)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(1024))
.addLast(new StringDecoder())
.addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String msgStr = (String) msg;
LOGUtils.printLog("服务端回复的消息 : "+msgStr);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String sendMsg = "Line Based Msg From Client "+System.getProperty("line.separator");
for(int i=0; i<100;i++){
ByteBuf byteBuf = Unpooled.copiedBuffer(sendMsg.getBytes());
ctx.writeAndFlush(byteBuf);
}
}
});
}
});
//同步等待链接成功
ChannelFuture sync = client.connect("127.0.0.1", 8080).sync();
sync.channel().closeFuture().sync();//同步等待客户端端口关闭
}finally {
clientWork.shutdownGracefully();//关闭资源.
}
}
}