2019独角兽企业重金招聘Python工程师标准>>>
Netty解决TCP的粘包和分包(一)
关于TCP的粘包和分包:http://my.oschina.net/xinxingegeya/blog/484824
Netty分包
分包的解决办法:
1、消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。
2、包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。
3、将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段
4、更复杂的自定义应用层协议
而在netty提供了两个解码器,可以进行分包的操作,这两个解码器分别是:
DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包)
FixedLengthFrameDecoder(使用定长的报文来分包)
来看一下这两种解码器是如何进行分包的。
DelimiterBasedFrameDecoder分包
先看一个实例,一个netty的example,github地址
https://github.com/netty/netty/tree/master/example/src/main/java/io/netty/example/securechat
只是其中的一个设置编解码器的类。
package com.usoft.chat;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.ssl.SslContext;
/**
* Creates a newly configured {@link ChannelPipeline} for a new channel.
*/
public class SecureChatServerInitializer extends
ChannelInitializer {
private final SslContext sslCtx;
public SecureChatServerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// Add SSL handler first to encrypt and decrypt everything.
// In this example, we use a bogus certificate in the server side
// and accept any invalid certificates in the client side.
// You will need something more complicated to identify both
// and server in the real world.
pipeline.addLast(sslCtx.newHandler(ch.alloc()));
// On top of the SSL handler, add the text line codec.
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters
.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// and then business logic.
pipeline.addLast(new SecureChatServerHandler());
}
}
其中这行代码就是设置解码器来分包的,
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
如下是这个类的属性字段,
private final ByteBuf[] delimiters; // 分包的分隔符数组
private final int maxFrameLength; //报文(帧)的最大的长度
private final boolean stripDelimiter; // 是否除去分隔符(如果数据包中含有分隔符,不影响)
private final boolean failFast; // 为true是说发现读到的数据已经超过了maxFrameLength了,立即报TooLongFrameException,如果为false就是读完整个帧数据后再报
private boolean discardingTooLongFrame; //是否抛弃超长的帧
private int tooLongFrameLength; //就是说出现了超长帧,那这个帧的长度到底是多少,就是这个长度,一般来说是在发现当前buffer的可读数据超过最大帧时候进行设置
其实这个例子都不会造成粘包,因为客户端的每次输入然后回车都会使客户端进行一次writeAndFlush。这里只不过是演示了 DelimiterBasedFrameDecoder的用法。同时使用这个解码器也可以实现分包。
FixedLengthFrameDecoder分包
实例如下,服务器端代码,
package com.usoft.demo1;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* 定长解码 服务器端
*
* @author xwalker
*/
public class Server {
public static void main(String[] args) throws Exception {
int port = 8000;
new Server().bind(port);
}
public void bind(int port) throws Exception {
//接收客户端连接用
EventLoopGroup bossGroup = new NioEventLoopGroup();
//处理网络读写事件
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//配置服务器启动类
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))//配置日志输出
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//设置定长解码器 长度设置为30
ch.pipeline().addLast(new StringDecoder());//设置字符串解码器 自动将报文转为字符串
ch.pipeline().addLast(new Serverhandler());//处理网络IO 处理器
}
});
//绑定端口 等待绑定成功
ChannelFuture f = b.bind(port).sync();
//等待服务器退出
f.channel().closeFuture().sync();
} finally {
//释放线程资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.usoft.demo1;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 服务器handler
*
* @author xwalker
*/
public class Serverhandler extends ChannelHandlerAdapter {
private static final String MESSAGE = "It greatly simplifies and streamlines network programming such as TCP and UDP socket server.";
int counter = 0;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("接收客户端msg:[" + msg + "]");
ByteBuf echo = Unpooled.copiedBuffer(MESSAGE.getBytes());
ctx.writeAndFlush(echo);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端代码,
package com.usoft.demo1;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
* 客户端
* @author xwalker
*
*/
public class Client {
/**
* 链接服务器
* @param port
* @param host
* @throws Exception
*/
public void connect(int port,String host)throws Exception{
//网络事件处理线程组
EventLoopGroup group=new NioEventLoopGroup();
try{
//配置客户端启动类
Bootstrap b=new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)//设置封包 使用一次大数据的写操作,而不是多次小数据的写操作
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//设置定长解码器
ch.pipeline().addLast(new StringDecoder());//设置字符串解码器
ch.pipeline().addLast(new ClientHandler());//设置客户端网络IO处理器
}
});
//连接服务器 同步等待成功
ChannelFuture f=b.connect(host,port).sync();
//同步等待客户端通道关闭
f.channel().closeFuture().sync();
}finally{
//释放线程组资源
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port=8000;
new Client().connect(port, "127.0.0.1");
}
}
package com.usoft.demo1;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* 客户端处理器
*
* @author xwalker
*/
public class ClientHandler extends ChannelHandlerAdapter {
private static final String MESSAGE = "Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients.";
public ClientHandler() {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer(MESSAGE.getBytes()));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("接收服务器响应msg:[" + msg + "]");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
服务器端打印结果:
receive client msg:[Netty is a NIO client server f]
receive client msg:[ramework which enables quick a]
receive client msg:[nd easy development of network]
receive client msg:[ applications such as protocol]
客户端打印结果:
receive sever msg:[It greatly simplifies and stre]
receive sever msg:[amlines network programming su]
receive sever msg:[ch as TCP and UDP socket serve]
receive sever msg:[r.It greatly simplifies and st]
receive sever msg:[reamlines network programming ]
receive sever msg:[such as TCP and UDP socket ser]
receive sever msg:[ver.It greatly simplifies and ]
receive sever msg:[streamlines network programmin]
receive sever msg:[g such as TCP and UDP socket s]
receive sever msg:[erver.It greatly simplifies an]
receive sever msg:[d streamlines network programm]
receive sever msg:[ing such as TCP and UDP socket]
客户端需要和服务器端约定每个包的大小为定长的,这样服务器端才可以根据这个规则来分包。这里也是演示了FixedLengthFrameDecoder解码器的用法。
参考和引用:
http://my.oschina.net/imhoodoo/blog/357290
http://asialee.iteye.com/blog/1783842
============END============