tcp 在发消息的时候,把多个数据量小的数据,合并成一个大的数据块,这个叫封包;
例如:客户端发送了2个数据包,一个a,一个b给到服务端,由于服务端一次读取到字节数是不确定的,可能有以下4种情况;
1、服务端分别读取到了a和b,这样是正常的情况;
2.服务端一次读取到了a和b,那么这样就是粘包;
3.服务第一次读取到了a和b的一部分数据,第二次读取到了b的剩余部分,这就叫拆包;
4.服务端第一次读取到了a的一部分,第二次读取到了a的剩余部分和b的完整数据,这也叫拆包;
我们来看下案例
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.20.Final</version>
</dependency>
package com.example.demo.controller;
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 MyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup boss=new NioEventLoopGroup();
EventLoopGroup worker=new NioEventLoopGroup();
try {
ServerBootstrap bootstrap=new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new MyServerInitializer());
//绑定端口
ChannelFuture sync = bootstrap.bind(7777).sync();
//监听关闭
sync.channel().closeFuture().sync();
}finally {
//优雅关闭
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
package com.example.demo.controller;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import java.time.LocalDateTime;
import java.util.UUID;
//服务端业务处理程序
public class MyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
private int count;
//读取客户端传过来的数据
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
//可读字节
byte[]bytes=new byte[byteBuf.readableBytes()];
//读取到字节数据中
byteBuf.readBytes(bytes);
System.out.println("服务器接收数据:"+new String(bytes, CharsetUtil.UTF_8));
this.count++;
System.out.println("服务器接收数据量:"+this.count);
//给客户端回写数据
channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer(UUID.randomUUID().toString()+" ",CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
package com.example.demo.controller;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
//服务端初始化器
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new MyServerHandler());
}
}
package com.example.demo.controller;
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 MyClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup boss=new NioEventLoopGroup();
try {
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(boss)
.channel(NioSocketChannel.class)
.handler(new MyClientInitializer());
//绑定端口
ChannelFuture sync = bootstrap.connect("127.0.0.1",7777);
//监听关闭
sync.channel().closeFuture().sync();
}finally {
//优雅关闭
boss.shutdownGracefully();
}
}
}
package com.example.demo.controller;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
//客户端初始化器
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new MyClientHandler());
}
}
package com.example.demo.controller;
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 java.nio.charset.Charset;
//客户端处理程序
public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
private int count;
//给服务端写数据
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//发送10次数据
for (int i = 0; i < 10; i++) {
ctx.writeAndFlush(Unpooled.copiedBuffer("你好"+i+" ",CharsetUtil.UTF_8));
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
//读取服务器传过来的数据
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
byte[]bytes=new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
System.out.println("客户端接收消息是:"+new String(bytes,CharsetUtil.UTF_8));
this.count++;
System.out.println("客户端接收数据量是:"+this.count);
}
}
在这里可以看到,服务器一次性接收了所有,客户端只接收了一次
我们在启动一个客户端
可以看到,12粘包,345粘包,67粘包,89粘包
接下来我们通过协议包和自定义编码解码器来解决,粘包,拆包问题
package com.example.demo.controller;
//协议包
public class MessageProtocol {
//长度
private int len;
//内容
private byte[]content;
public void setLen(int len) {
this.len = len;
}
public void setContent(byte[] content) {
this.content = content;
}
public int getLen() {
return len;
}
public byte[] getContent() {
return content;
}
}
package com.example.demo.controller;
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 MyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup boss=new NioEventLoopGroup();
EventLoopGroup worker=new NioEventLoopGroup();
try {
ServerBootstrap bootstrap=new ServerBootstrap();
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new MyServerInitializer());
//绑定端口
ChannelFuture sync = bootstrap.bind(7777).sync();
//监听关闭
sync.channel().closeFuture().sync();
}finally {
//优雅关闭
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
package com.example.demo.controller;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import java.time.LocalDateTime;
import java.util.UUID;
//服务端业务处理程序
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
private int count;
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
//读取客户端传过来的数据
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
//当我们读取数据的时候 需要先在初始化器类加入 解码器
int len=messageProtocol.getLen();
byte[]content=messageProtocol.getContent();
System.out.println();
System.out.println("服务端接收内容如下");
System.out.println("长度:"+len);
System.out.println("内容:"+new String(content,CharsetUtil.UTF_8));
this.count++;
System.out.println("消息包数量:"+count);
//给客户端回写数据 当我们写入数据的时候 就需要在初始化器那个类 加入编码器
String str=UUID.randomUUID().toString();
int length=str.length();
byte[]bytes=str.getBytes();
MessageProtocol m=new MessageProtocol();
m.setLen(length);
m.setContent(bytes);
channelHandlerContext.writeAndFlush(m);
System.out.println();
}
}
package com.example.demo.controller;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
//服务端初始化器
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//加入解码器
pipeline.addLast(new MyMessageDecoder());
//加入编码器
pipeline.addLast(new MyMessageEncoder());
//加入业务处理程序
pipeline.addLast(new MyServerHandler());
}
}
package com.example.demo.controller;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
//解码器
public class MyMessageDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
System.out.println("解码器被调用");
//解析字节缓冲区
int len=byteBuf.readInt();
byte[]bytes=new byte[len];
byteBuf.readBytes(bytes);
//封装协议包对象 传递给下一个handler处理
MessageProtocol messageProtocol=new MessageProtocol();
messageProtocol.setLen(len);
messageProtocol.setContent(bytes);
list.add(messageProtocol);
}
}
package com.example.demo.controller;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
//编码器
public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol, ByteBuf byteBuf) throws Exception {
System.out.println("编码器被调用");
//把协议包写入 字节缓冲区
byteBuf.writeInt(messageProtocol.getLen());
byteBuf.writeBytes(messageProtocol.getContent());
}
}
package com.example.demo.controller;
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 MyClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup boss=new NioEventLoopGroup();
try {
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(boss)
.channel(NioSocketChannel.class)
.handler(new MyClientInitializer());
//绑定端口
ChannelFuture sync = bootstrap.connect("127.0.0.1",7777);
//监听关闭
sync.channel().closeFuture().sync();
}finally {
//优雅关闭
boss.shutdownGracefully();
}
}
}
package com.example.demo.controller;
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 java.nio.charset.Charset;
//客户端处理程序
public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
private int count;
//给服务端写数据
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//当我们写入数据的时候 就需要先在初始化器类 加入编码器
for (int i = 0; i <5 ; i++) {
String str="你好啊";
byte[]content=str.getBytes();
int len=content.length;
MessageProtocol messageProtocol=new MessageProtocol();
messageProtocol.setContent(content);
messageProtocol.setLen(len);
ctx.writeAndFlush(messageProtocol);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
System.out.println();
//当我们读取数据的时候 就需要在初始化器类 先加入解码器
System.out.println("客户端接收到内容如下");
System.out.println("长度:"+messageProtocol.getLen());
System.out.println("内容:"+new String(messageProtocol.getContent(),CharsetUtil.UTF_8));
this.count++;
System.out.println("消息包数量:"+this.count);
System.out.println();
}
}
package com.example.demo.controller;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
//客户端初始化器
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//加入编码器
pipeline.addLast(new MyMessageEncoder());
//加入解码器
pipeline.addLast(new MyMessageDecoder());
//业务处理程序
pipeline.addLast(new MyClientHandler());
}
}
可以看到客户端给服务器发送了5次,服务器接收了5次
服务器给客户端回写了5次,客户端接收了5次
这样就解决了粘包,拆包问题