在通讯协议中,粘包,拆包是常发生的事情,而netty则很好的给出了三种解决方式,下面分别介绍:
一;利用LineBasedFrameDecoder解决TCP粘包问题
直接上代码,
Client类:
package stickorApartPackageResolveOne.one;
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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolveOne.one
* @ClassName: Client
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/12 16:48
* @Version: 1.0
* *LineBasedFrameDecoder以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,
* * 同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,
* * 就会抛出异常,同时忽略掉之前读到的异常码流。
* * StringDecoder的功能非常简单,就是将接收到的对象转换成字符串,
* * 然后继续调用后面的handler。LineBasedFrameDecoder+StringDecoder组合就是按行切换的文本解码器,
* * 它被设计用来支持TCP的粘包和拆包。
*/
public class Client {
public static void main(String[] args) {
int port = 8080;
String host = "127.0.0.1";
new Client().connect(port, host);
}
private void connect(int port, String host) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bst = new Bootstrap();
bst.group(eventLoopGroup);
bst.channel(NioSocketChannel.class);
bst.option(ChannelOption.TCP_NODELAY, true);
bst.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture f = bst.connect(host, port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
ClientHandler类
package stickorApartPackageResolveOne.one; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import java.util.logging.Logger; /** * @ProjectName: nettyTes * @Package: stickorApartPackageResolveOne.one * @ClassName: ClientHandler * @Author: Administrator * @Description: ${description} * @Date: 2019/10/12 17:03 * @Version: 1.0 */ public class ClientHandler extends ChannelHandlerAdapter { private static final Logger logger = Logger .getLogger(ClientHandler.class.getName()); private int counter; String req = ("QUERY TIME ORDER" + System.getProperty("line.separator")); @Override public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 30; i++) { ctx.writeAndFlush(Unpooled.copiedBuffer(req.getBytes())); }} @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String body = (String) msg; System.out.println("Now is : " + body + " ; the counter is : " + ++counter); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 释放资源 logger.warning("Unexpected exception from downstream : " + cause.getMessage()); ctx.close(); } }
Server类
package stickorApartPackageResolveOne.one;
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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolveOne
* @ClassName: Timeserver
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/12 16:03
* @Version: 1.0
*/
public class Server {
public static void main(String args[]) {
int port = 8080;
new Server().bind(port);
}
private void bind(int port) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
EventLoopGroup eventLoopGroup1 = new NioEventLoopGroup();
try {
ServerBootstrap sbt = new ServerBootstrap();
sbt.group(eventLoopGroup, eventLoopGroup1);
sbt.channel(NioServerSocketChannel.class);
sbt.option(ChannelOption.SO_BACKLOG, 1024);
sbt.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//利用LineBasedFrameDecoder解决TCP粘包问题
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new ServerHandler());
}
});
ChannelFuture cf = sbt.bind(port).sync();
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
eventLoopGroup1.shutdownGracefully();
}
}
}
ServerHandler类
package stickorApartPackageResolveOne.one;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolveOne
* @ClassName: ServerHandler
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/12 16:20
* @Version: 1.0
*/
public class ServerHandler extends ChannelHandlerAdapter {
int counter;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String)msg;
System.out.println("The time server receive order : " + body + " ; the counter is : " + ++counter);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
currentTime = currentTime + System.getProperty("line.separator");
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.writeAndFlush(resp);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
先直接上结果:
客户端:
服务端:
LineBasedFrameDecoder和StringDecoder的原理分析
LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有\n或者以\r\n气如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。
StringDecoder的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的handler。LineBasedFrameDecoder+StringDecoder组合就是按行切换的文本解码器,它被设计用来支持TCP的粘包和拆包。
二、利用DelimiterBasedFrameDecoder
Client1类:
package stickorApartPackageResolve.two;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolve.two
* @ClassName: Client1
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/14 9:18
* @Version: 1.0
*/
public class Client1 {
public static void main(String[] args) {
int port = 8080;
String host = "127.0.0.1";
new Client1().connect(host,port);
}
private void connect(String host, int port) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bs = new Bootstrap();
bs.group(eventLoopGroup);
bs.channel(NioSocketChannel.class);
bs.option(ChannelOption.TCP_NODELAY, true);
bs.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ByteBuf buffer = Unpooled.copiedBuffer("$_".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,buffer));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new Client1Handler());
}
});
ChannelFuture f = bs.connect(host,port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
Client1Handler类
package stickorApartPackageResolve.two;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolve.two
* @ClassName: Client1Handler
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/14 14:35
* @Version: 1.0
*/
public class Client1Handler extends ChannelHandlerAdapter {
private int counter;
private String req = "Hi,XXX! Welcome to netty.$_";
@Override
public void channelActive(ChannelHandlerContext ctx){
for(int i=0; i<10; i++){
ctx.writeAndFlush(Unpooled.copiedBuffer(req.getBytes()));
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
String body = (String) msg;
System.out.println("this is : " + ++counter + "time recive " + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 释放资源
ctx.close();
}
}
Server1类
package stickorApartPackageResolve.two;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolve.two
* @ClassName: Server1
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/14 8:54
* @Version: 1.0
*/
public class Server1 {
public static void main(String[] args) {
int port = 8080;
new Server1().bind(port);
}
private void bind(int port) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
EventLoopGroup eventLoopGroup1 = new NioEventLoopGroup();
try {
ServerBootstrap sbt = new ServerBootstrap();
sbt.group(eventLoopGroup,eventLoopGroup1);
sbt.channel(NioServerSocketChannel.class);
sbt.option(ChannelOption.SO_BACKLOG, 1024);
sbt.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ByteBuf buffer = Unpooled.copiedBuffer("$_".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,buffer));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new Server1Handler());
}
});
ChannelFuture f = sbt.bind(port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
eventLoopGroup.shutdownGracefully();
eventLoopGroup1.shutdownGracefully();
}
}
}
Server1Handler类
package stickorApartPackageResolve.two;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolve.two
* @ClassName: Server1Handler
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/14 9:10
* @Version: 1.0
*/
public class Server1Handler extends ChannelHandlerAdapter {
int counter;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
String body = (String)msg;
System.out.println("The time server receive order : " + body + " ; the counter is : " + ++counter);
body = body + "$_";
ByteBuf bf = Unpooled.copiedBuffer(body.getBytes());
ctx.writeAndFlush(bf);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
结果:
服务端
客户端
通过对DelimiterBasedFrameDecoder的使用,我们可以自动完成以分隔符作为码流结束标识的消息的解码,
三、 FixedLengthFrameDecoder
FixedLengtl1FrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP的粘包/拆包问题,非常实用。下面我们通过一个应用实例对其用法进行讲解。
Server1类:
package stickorApartPackageResolve.two;
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;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolve.two
* @ClassName: Server1
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/14 8:54
* @Version: 1.0
*/
public class Server1 {
public static void main(String[] args) {
int port = 8080;
new Server1().bind(port);
}
private void bind(int port) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
EventLoopGroup eventLoopGroup1 = new NioEventLoopGroup();
try {
ServerBootstrap sbt = new ServerBootstrap();
sbt.group(eventLoopGroup,eventLoopGroup1);
sbt.channel(NioServerSocketChannel.class);
sbt.option(ChannelOption.SO_BACKLOG, 1024);
sbt.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//会按照这里给定的长度对消息进行截取
socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(12));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new Server1Handler());
}
});
ChannelFuture f = sbt.bind(port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
eventLoopGroup.shutdownGracefully();
eventLoopGroup1.shutdownGracefully();
}
}
}
Server1Handler类:
package stickorApartPackageResolve.two;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolve.two
* @ClassName: Server1Handler
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/14 9:10
* @Version: 1.0
*/
public class Server1Handler extends ChannelHandlerAdapter {
int counter;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
String body = (String)msg;
System.out.println("The time server receive order : " + body + " ; the counter is : " + ++counter);
ByteBuf bf = Unpooled.copiedBuffer(body.getBytes());
ctx.writeAndFlush(bf);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
Client1类:
package stickorApartPackageResolve.two;
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;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolve.two
* @ClassName: Client1
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/14 9:18
* @Version: 1.0
*/
public class Client1 {
public static void main(String[] args) {
int port = 8080;
String host = "127.0.0.1";
new Client1().connect(host,port);
}
private void connect(String host, int port) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bs = new Bootstrap();
bs.group(eventLoopGroup);
bs.channel(NioSocketChannel.class);
bs.option(ChannelOption.TCP_NODELAY, true);
bs.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(12));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new Client1Handler());
}
});
ChannelFuture f = bs.connect(host,port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
Client1Handler类:
package stickorApartPackageResolve.two;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* @ProjectName: nettyTes
* @Package: stickorApartPackageResolve.two
* @ClassName: Client1Handler
* @Author: Administrator
* @Description: ${description}
* @Date: 2019/10/14 14:35
* @Version: 1.0
*/
public class Client1Handler extends ChannelHandlerAdapter {
private int counter;
private String req = "Hi!xiaoming!";
@Override
public void channelActive(ChannelHandlerContext ctx){
for(int i=0; i<10; i++){
ctx.writeAndFlush(Unpooled.copiedBuffer(req.getBytes()));
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
String body = (String) msg;
System.out.println("this is : " + ++counter + "time recive " + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 释放资源
ctx.close();
}
}
结果:
服务端
客户端:
利用FixedLengthFrameDecoder解码器,无论一次接收到多少数据报,它都会按照构造函数中设置的固定长度进行解码,如果是半包消息,FixedLengthFrameDecoder会缓存半包消息并等待下个包到达后进行拼包,直到读取到一个完整的包。