周五没事干,对之前文章进行整理下。来到现公司已经有半年时间。从当初对网络编程的懵懵懂懂,到现在使用Netty对服务端开发完成并上线。
今天这篇文章总结下在这个过程困扰我的最多点--“TCP协议进行通信,出现粘包和拆包”。使用TCP协议通信一定会出现粘包和拆包?为什么加上固定包头就可以解决粘包和拆包?
首先我们先分析下,什么情况下会出现粘包?是TCP通信都有可能出现粘包?来通过一个Demo模拟下TCP协议通信,一起分析下粘包出现的可能性。来来来!
1.1 实例不出现粘包
1.1.1 代码实现
客户端以每500毫秒间隔向服务端发送相同字节数组。
1. 服务端的配置类
public class TcpServer {
private final int port;
public TcpServer(int port) {
this.port = port;
}
public void init() {
NioEventLoopGroup boss = new NioEventLoopGroup();//主线程组
NioEventLoopGroup work = new NioEventLoopGroup();//工作线程组
try {
ServerBootstrap bootstrap = new ServerBootstrap();//引导对象
bootstrap.group(boss, work);//配置工作线程组
bootstrap.channel(NioServerSocketChannel.class);//配置为NIO的socket通道
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {//绑定通道参数
ch.pipeline().addLast("logging", new LoggingHandler("DEBUG"));//设置log监听器,并且日志级别为debug,方便观察运行流程
ch.pipeline().addLast("encode", new EncoderHandler());//编码器。发送消息时候用过
ch.pipeline().addLast("decode", new DecoderHandler());//解码器,接收消息时候用
ch.pipeline().addLast("handler", new TcpServerHandler());//业务处理类,最终的消息会在这个handler中进行业务处理
}
});
//使用了Future来启动线程,并绑定了端口
ChannelFuture future = bootstrap.bind(port).sync();
//以异步的方式关闭端口
future.channel().closeFuture().sync();
} catch (Exception e) {
} finally {
work.shutdownGracefully();
//出现异常后,关闭线程组
boss.shutdownGracefully();
}
}
}
2. 服务端解码器(decode)
public class DecoderHandler extends ByteToMessageDecoder {
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("byteBuf的容量为:" + in.capacity());
System.out.println("byteBuf的可读容量为:" + in.readableBytes());
System.out.println("byteBuf的可写容量为:" + in.writableBytes());
byte[] data = new byte[in.readableBytes()];
//读取核心的数据
in.readBytes(data);
out.add(data);
}
}
3. 服务端编码器(encode)
public class EncoderHandler extends MessageToByteEncoder {
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
out.writeBytes((byte[]) msg);
}
}
4. 服务端的ChannelHandler
public class TcpServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
byte[] recevierByte = (byte[]) msg;
String recevierString = ByteTransform.bytesToHexString(recevierByte);
System.out.println("-------------------长度为:" + recevierString.length());
System.out.println("---tcp服务接受设备端加密数据:" + recevierString);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
/**
* 客户端断开连接触发方法
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
}
/**
* 方法中报错,触发方法
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
5. 服务端启动类
public class StartServer {
public static void main(String[] args) {
try {
new TcpServer(5566).init();
} catch (Exception e) {
e.printStackTrace();
}
}
}
6. 工具类(知道方法是干嘛的就行)
public class ByteTransformIntUtil {
/**
* 根据byte转化为int
*/
public static int getIntFromBytes(byte high_h, byte high_l, byte low_h, byte low_l) {
return (high_h & 0xff) << 24 | (high_l & 0xff) << 16 | (low_h & 0xff) << 8 | low_l & 0xff;
}
/**
* 根据byte转化为int
*/
public static int getIntFromBytes(byte low_h, byte low_l) {
return ByteTransformIntUtil.getIntFromBytes((byte)0,(byte)0,low_h,low_l);
}
}
7. 客户端的配置类
public class MyRPCClient {
public void start(String host, int port) throws Exception {
//定义工作线程组
EventLoopGroup worker = new NioEventLoopGroup();
try {
//注意:client使用的是Bootstrap
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker)
.channel(NioSocketChannel.class) //注意:client使用的是NioSocketChannel
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {