文章目录
1 TCP 粘包和拆包
- TCP是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的 socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样做虽然提高了效率,但是接收端就难于分辨出完整的数据包了,因为面向流的通信是无消息保护边界的。
- 由于TCP无消息保护边界, 需要在接收端处理消息边界问题,即我们所说的粘包、拆包问题。
- TCP粘包、拆包图解:
假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下四种情况:- 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2,没有粘包和拆包。
- 服务端一次接受到了两个数据包,D1 和 D2 粘合在一起,称之为 TCP 粘包。
- 服务端分两次读取到了数据包,第一次读取到了完整的 D1 包和 D2 包的部分内容D2_1,第二次读取到了 D2 包的剩余内容 D2 _2,这称之为 TCP 拆包。
- 服务端分两次读取到了数据包,第一次读取到了D1包的部分内容 D1_1,第二次读取到了 D1包的剩余部分内容 D1_2和完整的 D2 包。
上层协议为了对消息进行区分,往往采用以下几种方式:
- 将回车换行符作为消息结束符,例如 FTP 协议,这种方式在文本协议中应用比较广泛;
- 将特殊的分隔符作为泭息的结束标志,回车换行符就是一种特殊的结束分隔符;
- 消息长度固定,累计读取到长度总和为定长 LEN 的报文后,就认为读取到了一个完整的消息;将计数器置位,重新开始读取下一个数据报;
- 通过在消息头中定义长度字段来标识消息的总长度。
2 TCP 粘包和拆包现象实例
在编写 Netty 程序时,如果没有做处理,就会发生粘包和拆包的问题,查看下面代码:
服务端:
package bin.netty.tcp粘包拆包;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyServer {
public static void main(String[] args) throws Exception {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 业务处理,读取客户端的消息并显示
pipeline.addLast(new MyServerHandler1());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(9999).sync();
channelFuture.addListener(cf -> {
if (cf.isSuccess()) {
System.out.println("listen on 9999");
}
});
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
MyServerHandler1:
package bin.netty.tcp粘包拆包;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyServerHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress() + ": ");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
System.out.println("客户端的消息:" + new String(bytes, CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
客户端:
package bin.netty.tcp粘包拆包;
import bin.netty.inandoutbound.MyLongMessageDecoder;
import bin.netty.inandoutbound.MyLongMessageEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyClient {
public static void main(String[] args) throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyClientHandler1());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9999).sync();
channelFuture.addListener(cf -> {
if (cf.isSuccess()) {
System.out.println("connect to server");
}
});
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
MyClientHandler1:
package bin.netty.tcp粘包拆包;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyClientHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 5; i++) {
ctx.writeAndFlush(Unpooled.copiedBuffer("消息序号:" + i, CharsetUtil.UTF_8));
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
运行结果如下:
/127.0.0.1:51653:
客户端的消息:消息序号:0消息序号:1消息序号:2消息序号:3消息序号:4
/127.0.0.1:51686:
客户端的消息:消息序号:0
客户端的消息:消息序号:1
客户端的消息:消息序号:2
客户端的消息:消息序号:3
客户端的消息:消息序号:4
运行两个客户端,但是接收到的消息顺序不一样的。
3 TCP 粘包和拆包解决方案
3.1 LineBasedFrameDecoder 解决 TCP 粘包问题
LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf 中的可读字节, 判断看是否有 “\n” 或者 “\ r\n”, 如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度=。如果连续读取到最大长度后仍然没有发现换行符, 就会抛出异常, 同时忽略掉之前读到的异常码流。
服务端:
package bin.netty.tcp粘包拆包.lineframedecoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Date;
/**
* LineBasedFrameDecoder + StringDecoder 解决 Tcp 粘包
*
* @author liyibin
* @date 2021-07-10
*/
public class TimeServer {
public static void main(String[] args) throws Exception {
int port = 9999;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception ignore) {
}
}
// 启动服务器
new TimeServer().start(port);
}
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder());
pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new TimeServerHandler());
}
});
ChannelFuture cf = serverBootstrap.bind(port).sync();
System.out.println("listen on " + port);
// 等待服务器监听窗口关闭
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class TimeServerHandler extends SimpleChannelInboundHandler<String> {
private int counter;
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("接收到客户端请命令:" + msg + ", counter=" + ++counter);
String content = "NOW".equalsIgnoreCase(msg) ? new Date().toString() : "BAD COMMAND";
// 字符串加上换行符表示结束
ctx.writeAndFlush(content + System.getProperty("line.separator"));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
}
客户端:
package bin.netty.tcp粘包拆包.lineframedecoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
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;
import io.netty.handler.codec.string.StringEncoder;
import java.net.InetSocketAddress;
/**
* @author liyibin
* @date 2021-07-10
*/
public class TimeClient {
public static void main(String[] args) throws Exception {
int port = 9999;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception ignore){
}
}
new TimeClient().connect("127.0.0.1", port);
}
public void connect(String host, int port) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder());
pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new TimeClientHandler());
}
});
ChannelFuture ch = bootstrap.connect(new InetSocketAddress(host, port)).sync();
System.out.println("connect to server");
ch.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
private static class TimeClientHandler extends SimpleChannelInboundHandler<String> {
private String timeCmd = "NOW" + System.getProperty("line.separator");
private int counter;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 100; i++) {
// 请求资源
ctx.writeAndFlush(timeCmd);
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Now is: " + msg + ", counter=" + ++counter);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
}
3.2 DelimiterBasedFrameDecoder 分隔符
DelimiterBasedFrameDecoder 可以自动完成以分隔符作为码流结束标识的消息的解码。
服务端:
package bin.netty.tcp粘包拆包.自定义分隔符;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Date;
/**
* DelimiterBasedFrameDecoder + StringDecoder 解决 Tcp 粘包
*
* @author liyibin
* @date 2021-07-10
*/
public class TimeServer {
public static void main(String[] args) throws Exception {
int port = 9999;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception ignore) {
}
}
// 启动服务器
new TimeServer().start(port);
}
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder());
// 已 $ 作为分隔符
pipeline.addLast(new DelimiterBasedFrameDecoder(
1024, Unpooled.copiedBuffer("$".getBytes())));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new TimeServerHandler());
}
});
ChannelFuture cf = serverBootstrap.bind(port).sync();
System.out.println("listen on " + port);
// 等待服务器监听窗口关闭
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class TimeServerHandler extends SimpleChannelInboundHandler<String> {
private int counter;
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("接收到客户端请命令:" + msg + ", counter=" + ++counter);
String content = "NOW".equalsIgnoreCase(msg) ? new Date().toString() : "BAD COMMAND";
// 字符串加上分隔符$表示结束
ctx.writeAndFlush(content + "$");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
}
客户端:
package bin.netty.tcp粘包拆包.自定义分隔符;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.net.InetSocketAddress;
/**
* @author liyibin
* @date 2021-07-10
*/
public class TimeClient {
public static void main(String[] args) throws Exception {
int port = 9999;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception ignore){
}
}
new TimeClient().connect("127.0.0.1", port);
}
public void connect(String host, int port) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder());
// 已 $ 作为分隔符
pipeline.addLast(new DelimiterBasedFrameDecoder(
1024, Unpooled.copiedBuffer("$".getBytes())));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new TimeClientHandler());
}
});
ChannelFuture ch = bootstrap.connect(new InetSocketAddress(host, port)).sync();
System.out.println("connect to server");
ch.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
private static class TimeClientHandler extends SimpleChannelInboundHandler<String> {
private int counter;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 100; i++) {
// 请求资源
ctx.writeAndFlush("NOW$");
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Now is: " + msg + ", counter=" + ++counter);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
}
3.3 FixedLengthFrameDecoder 固定长度
FixedLengthFrameDecoder 是固定长度解码器,它能够按照指定的长度对消息进行自动解码,开发者不需要考虑 TCP 的粘包/拆包问题。
服务端:
package bin.netty.tcp粘包拆包.固定长度;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
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.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Date;
/**
* FixedLengthFrameDecoder + StringDecoder 解决 Tcp 粘包
*
* @author liyibin
* @date 2021-07-10
*/
public class TimeServer {
public static void main(String[] args) throws Exception {
int port = 9999;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception ignore) {
}
}
// 启动服务器
new TimeServer().start(port);
}
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder());
// 固定长度 3
pipeline.addLast(new FixedLengthFrameDecoder(3));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new TimeServerHandler());
}
});
ChannelFuture cf = serverBootstrap.bind(port).sync();
System.out.println("listen on " + port);
// 等待服务器监听窗口关闭
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class TimeServerHandler extends SimpleChannelInboundHandler<String> {
private int counter;
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("接收到客户端请命令:" + msg + ", counter=" + ++counter);
String content = "now".equalsIgnoreCase(msg) ? new Date().toString() : "BAD COMMAND";
// 字符串加上分隔符$表示结束
ctx.writeAndFlush(content);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
}
客户端:使用 Telnet 进行测试
telnet host ip
服务器回显内容
3.4 自定义协议 + 编解码器
- 使用自定义协议 + 编解码器来解决。
- 关键就是要解决服务器端每次读取数据长度的问题。这个问题解决,就不会出现服务器多读或少读数据的问题,从而避免的 TCP 粘包,拆包 。
- 看下面的代码示例:
要求客户端发送 5 个 Message 对象, 客户端每次发送一个 Message 对象。
服务器端每次接收一个 Message, 分 5 次进行解码, 每读取到 一个 Message , 会回复一个Message 对象 给客户端。
MyMessageEncoder:
package bin.netty.protocoltcp;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.ReplayingDecoder;
import io.netty.util.CharsetUtil;
import java.util.List;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyMessageEncoder extends MessageToByteEncoder<MyMessage> {
@Override
protected void encode(ChannelHandlerContext ctx, MyMessage msg, ByteBuf out) throws Exception {
// 写入数据长度
out.writeInt(msg.getLen());
// 写入数据内容
out.writeBytes(msg.getContent().getBytes(CharsetUtil.UTF_8));
}
}
MyMessageDecoder:
package bin.netty.protocoltcp;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import io.netty.util.CharsetUtil;
import java.util.List;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyMessageDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyMessageDecoder decode 方法被调用");
// 数据长度
int dataLen = in.readInt();
// 数据
byte[] data = new byte[dataLen];
in.readBytes(data);
MyMessage myMessage = new MyMessage();
myMessage.setLen(dataLen);
myMessage.setContent(new String(data, CharsetUtil.UTF_8));
// 给下一个 handler处理
out.add(myMessage);
}
}
服务端:
package bin.netty.protocoltcp;
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 io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* 使用 自定义协议+编解码器 解决 tcp 粘包拆包
*
* @author liyibin
* @date 2021-06-27
*/
public class MyServer {
public static void main(String[] args) throws Exception {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 编码器
pipeline.addLast(new MyMessageEncoder());
// 解码器
pipeline.addLast(new MyMessageDecoder());
// 业务处理,读取客户端的消息并显示
pipeline.addLast(new MyServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(9999).sync();
channelFuture.addListener(cf -> {
if (cf.isSuccess()) {
System.out.println("listen on 9999");
}
});
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
MyServerHandler:
package bin.netty.protocoltcp;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
import java.nio.charset.Charset;
import java.util.UUID;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyServerHandler extends SimpleChannelInboundHandler<MyMessage> {
private int count;
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyMessage msg) throws Exception {
//接收到数据,并处理
System.out.println("服务器接收到信息如下:len=" + msg.getLen() + ", content=" + msg.getContent());
System.out.println("服务器接收到消息包数量=" + (++this.count));
//回复消息
String responseContent = UUID.randomUUID().toString();
//构建一个协议包
MyMessage myMessage = new MyMessage();
myMessage.setLen(responseContent.getBytes(CharsetUtil.UTF_8).length);
myMessage.setContent(responseContent);
ctx.writeAndFlush(myMessage);
}
}
客户端:
package bin.netty.protocoltcp;
import bin.netty.tcp粘包拆包.MyClientHandler1;
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;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyClient {
public static void main(String[] args) throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 编码器
pipeline.addLast(new MyMessageEncoder());
// 解码器
pipeline.addLast(new MyMessageDecoder());
pipeline.addLast(new MyClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9999).sync();
channelFuture.addListener(cf -> {
if (cf.isSuccess()) {
System.out.println("connect to server");
}
});
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
MyClientHandler:
package bin.netty.protocoltcp;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyClientHandler extends SimpleChannelInboundHandler<MyMessage> {
private int count;
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyMessage msg) throws Exception {
int len = msg.getLen();
String content = msg.getContent();
System.out.println("客户端接收到消息:长度=" + len + ", content =" + content);
System.out.println("客户端接收消息数量=" + (++this.count));
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 5; i++) {
String content = "消息序号:" + i;
MyMessage myMessage = new MyMessage();
myMessage.setLen(content.getBytes(CharsetUtil.UTF_8).length);
myMessage.setContent(content);
// 写入消息
ctx.writeAndFlush(myMessage);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
运行结果如下:
MyMessageDecoder decode 方法被调用
客户端接收到消息:长度=36, content =731347db-4154-4c7d-9fc1-67db6057019b
客户端接收消息数量=1
MyMessageDecoder decode 方法被调用
客户端接收到消息:长度=36, content =9179b4f5-f3f8-43a6-8352-1ca32fdce7a6
客户端接收消息数量=2
MyMessageDecoder decode 方法被调用
客户端接收到消息:长度=36, content =7a3c34d9-3fd5-448b-9ff8-b2e88d3c5ef6
客户端接收消息数量=3
MyMessageDecoder decode 方法被调用
客户端接收到消息:长度=36, content =93c7d873-5c83-4ad4-be23-e9b007bbaac0
客户端接收消息数量=4
MyMessageDecoder decode 方法被调用
客户端接收到消息:长度=36, content =50221cac-5bae-4667-8a45-9bdf5d18f1d1
客户端接收消息数量=5
可以看到没有出现粘包和拆包的情况。