基本介绍:
TCP 是面向连接的,面向流的,提供高可靠性服务。 收发两端 (客户端 和 服务器端)都要有一一成对的 socket。因此,发送端为了将多个发给接收端的包,更有效的发送给对方,使用了优化方法(Nagle 算法), 将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样做虽然提高了效率,但是接收端就难于分辨出完整的数据包了,因为面向流通信是无消息保护边界的,需要接收端处理消息边界问题。
存在四种情况: 由于服务端一次读取到字节数是不确定的
- 服务端分两次读取到了两个 独立的数据包,分别是D1 和 D2, 没有粘包和拆包
- 服务端一次接受到了两个数据包,D1 和 D2 粘合在一起,称之为 TCP 粘包
- 服务端分两次读取到了数据包, 第一次读取到完整的 D1 包和 D2 包的部分内容,第二次读取到了 D2 包的剩余内容,这称为 TCP 拆包
- 服务端分两次读取到了数据包,第一次读取到了 D1 包的部分内容 D1_1, 第二次读取到了 D1包的剩余部分内容 D1_2 和 完整的D2包
粘包和拆包示例:
NettyServerHandler:
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class NettyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
private int count;
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
byte[] buffer = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(buffer);
// 将 buffer 转成字符串
String message = new String(buffer, StandardCharsets.UTF_8);
System.out.println("服务器端接收到数据 " + message);
System.out.println("服务器接收到消息量=" + (count++));
// 服务器回送数据给客户端, 回送一个随机 id
ByteBuf responseByteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString() + " ", StandardCharsets.UTF_8);
channelHandlerContext.writeAndFlush(responseByteBuf);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
NettyServerInitializer:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new NettyServerHandler());
}
}
NettyServerHandler:
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class NettyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
private int count;
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
byte[] buffer = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(buffer);
// 将 buffer 转成字符串
String message = new String(buffer, StandardCharsets.UTF_8);
System.out.println("服务器端接收到数据 " + message);
System.out.println("服务器接收到消息量=" + (count++));
// 服务器回送数据给客户端, 回送一个随机 id
ByteBuf responseByteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString() + " ", StandardCharsets.UTF_8);
channelHandlerContext.writeAndFlush(responseByteBuf);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
NettyClientHandler:
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.nio.charset.StandardCharsets;
public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
private int count;
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
byte[] buffer = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(buffer);
// 将 buffer 转成字符串
String message = new String(buffer, StandardCharsets.UTF_8);
System.out.println("客户端端接收到数据 " + message);
System.out.println("客户端接收到消息量=" + (count++));
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 使用客户端发送 10条数据, hello,doubily
for (int i = 0; i< 10; i++) {
ByteBuf buffer = Unpooled.copiedBuffer("hello,doubily" + i + " ", StandardCharsets.UTF_8);
ctx.writeAndFlush(buffer);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
NettyClientInitializer:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new NettyClientHandler());
}
}
NettyClient:
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 NettyClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class).handler(new NettyClientInitializer());
ChannelFuture channelFuture = bootstrap.connect("localhost", 9527).sync();
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
启动多客户端后服务器效果图:
TCP 粘包和拆包解决:
增加POJO,根据长度去获取数据信息
MessageProtocol:
public class MessageProtocol {
private int len;
private byte[] content;
public int getLen() {
return len;
}
public void setLen(int len) {
this.len = len;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
}
MessageDecoder:
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
public class MessageDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
System.out.println("MessageDecoder decode 被调用");
// 需要将得到的二进制字节码 转成 messageProtocol 数据包
int length = byteBuf.readInt();
byte[] content = new byte[length];
byteBuf.readBytes(content);
// 封装成 MessageProtocol 对象,放入 list, 传递到下一个 handler 进行处理
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setLen(length);
messageProtocol.setContent(content);
list.add(messageProtocol);
}
}
MessageEncoder:
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MessageEncoder extends MessageToByteEncoder<MessageProtocol> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol, ByteBuf byteBuf) throws Exception {
System.out.println("MessageEncoder encode 方法被调用");
byteBuf.writeInt(messageProtocol.getLen());
byteBuf.writeBytes(messageProtocol.getContent());
}
}
NettyServerHandler:
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class NettyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {
private int count;
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
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("长度 = " + len);
System.out.println("内容 = " + new String(content, StandardCharsets.UTF_8));
System.out.println("服务器接收到消息包数量 = " + (++count));
// 回复消息
String responseMsg = UUID.randomUUID().toString();
byte[] responseContent = responseMsg.getBytes(StandardCharsets.UTF_8);
int responseLen = responseContent.length;
// 构建协议包
MessageProtocol responseProtocol = new MessageProtocol();
responseProtocol.setLen(responseLen);
responseProtocol.setContent(responseContent);
channelHandlerContext.writeAndFlush(responseProtocol);
}
}
NettyServerInitializer:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new MessageDecoder()); // 解码器
pipeline.addLast(new MessageEncoder()); // 编码器
pipeline.addLast(new NettyServerHandler());
}
}
NettyServer:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new NettyServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(9527).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
NettyClientHandler:
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.nio.charset.StandardCharsets;
public class NettyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
private int count;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 使用客户端发送 10条数据, hello,doubily
for (int i = 0; i< 5; i++) {
String msg = "hello,doubily" + i + " ";
byte[] content = msg.getBytes(StandardCharsets.UTF_8);
int length = content.length;
// 创建协议包对象
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setLen(length);
messageProtocol.setContent(content);
ctx.writeAndFlush(messageProtocol);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
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("长度 = " + len);
System.out.println("内容 = " + new String(content, StandardCharsets.UTF_8));
System.out.println("客户端接收到消息包数量 = " + (++count));
}
}
NettyClientInitializer:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new MessageEncoder()); // 编码器
pipeline.addLast(new MessageDecoder()); // 解码器
pipeline.addLast(new NettyClientHandler());
}
}
NettyClient:
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 NettyClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class).handler(new NettyClientInitializer());
ChannelFuture channelFuture = bootstrap.connect("localhost", 9527).sync();
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}