什么是Tcp粘包和拆包问题?
因为Tcp是面向连接的,面向流的,提供高可靠的连接服务,因此发送端为了将多个发送给接收端的包更加有效的发送给对方,就会使用一种优化算法(Nagle),将多次间隔较小的小数据包合并成一个大的数据块,然后进行封装。这样做虽然提高了效率,但是接收端就无法分辨出到底有多少个数据包。(面向流的)通信是无消息保护边界的。
所以发送端发送的多个包可能被接收端当作一个包接收,这就是粘包问题。同时也可能一个包被拆开到多个包中,这就是拆包问题。
如何解决?
自定义协议+编解码
- 如果通信的双方都知道我们发送的数据以什么结构进行发送的话,我们按照对应的结构进行读取数据我们就能正确的读取到一个数据包,这就需要通信双方事先商量好发送的数据包的结构,这个事先商量就是定义协议。
- 然后因为我们通过网络传输都是以字节数据的形式进行传输,所以我们在发、收两端都需要对数据进行编解码,如将数据包对象的格式编码成对应的字节数据,将字节数据解码成数据包对象。
netty实现代码
服务器端
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import protocol.codec.ProtocolMsgDecoder;
import protocol.codec.ProtocolMsgEncoder;
import protocol.handler.ServerChannelHandler;
public class TcpServer {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtocolMsgDecoder());
pipeline.addLast(new ProtocolMsgEncoder());
pipeline.addLast(new ServerChannelHandler());
}
});
ChannelFuture channelFuture =
bootstrap.bind(9898).sync();
System.out.println("服务器启动成功...");
channelFuture.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
客户端
package protocol;
import codec.coder.ProtoClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import protocol.codec.ProtocolMsgDecoder;
import protocol.codec.ProtocolMsgEncoder;
import protocol.handler.TcpClientChannelHandler;
public class TcpClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup executors = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(executors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtocolMsgEncoder());
pipeline.addLast(new ProtocolMsgDecoder());
pipeline.addLast(new TcpClientChannelHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9898)
.sync();
channelFuture.channel().closeFuture().sync();
} finally {
executors.shutdownGracefully();
}
}
}
编码器与解码器
package protocol.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import protocol.ProtocolPackage;
import java.util.List;
public class ProtocolMsgDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int len = in.readInt();
byte[] bytes = new byte[len];
in.readBytes(bytes);
ProtocolPackage pack = new ProtocolPackage(len, bytes);
out.add(pack);
}
}
package protocol.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import protocol.ProtocolPackage;
public class ProtocolMsgEncoder extends MessageToByteEncoder<ProtocolPackage> {
@Override
protected void encode(ChannelHandlerContext ctx, ProtocolPackage msg, ByteBuf out) throws Exception {
out.writeInt(msg.getLen());
out.writeBytes(msg.getContent());
}
}
服务器端handler
package protocol.handler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
import protocol.ProtocolPackage;
import java.util.UUID;
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProtocolPackage> {
private int count = 0;
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProtocolPackage msg) throws Exception {
System.out.println("服务器第"+(++count)+"次收到数据");
System.out.println("服务器收到得数据长度:"+msg.getLen());
System.out.println("服务器收到得数据内容:"+new String(msg.getContent(),CharsetUtil.UTF_8));
writeToClient(ctx);
}
public void writeToClient(ChannelHandlerContext ctx){
String info = UUID.randomUUID().toString();
byte[] bytes = info.getBytes();
int len = bytes.length;
ProtocolPackage protocolPackage = new ProtocolPackage(len, bytes);
ctx.writeAndFlush(protocolPackage);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
}
}
客户端handler
package protocol.handler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
import protocol.ProtocolPackage;
import java.nio.charset.Charset;
public class TcpClientChannelHandler extends SimpleChannelInboundHandler<ProtocolPackage> {
private int cnt = 0;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// Channel channel = ctx.channel();
for (int i = 0; i < 5; i++) {
String info = "天气好热呀,想吃冰淇淋 ["+i+"]";
byte[] bytes = info.getBytes();
int len = bytes.length;
ProtocolPackage pck = new ProtocolPackage(len, bytes);
ctx.writeAndFlush(pck);
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProtocolPackage msg) throws Exception {
System.out.println("客户端第"+(++cnt)+"次收到数据");
System.out.println("客户端收到得数据长度为:"+msg.getLen());
System.out.println("客户端收到得数据信息为:"+new String(msg.getContent(), Charset.forName("utf-8")));
}
}
自定义数据包
package protocol;
public class ProtocolPackage {
private int len;
private byte[] content;
public ProtocolPackage() {
}
public ProtocolPackage(int len, byte[] content) {
this.len = len;
this.content = 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;
}
}