063:Netty实战与tcp协议粘包与拆包解决方案
1 回顾NIO核心设计思想和理念
课程内容:
1.回顾NIO的核心设计思想是什么?
2.为什么放弃原生Nio选用Netty框架
3.基于Netty实现客户端与服务器端通讯
4.解决tcp协议粘包与拆包解决方案
Nio核心设计思想:非阻塞式 、(选择器)io多路复用原则、缓冲区(提高读写效率)
2 为什么放弃NIO使用Netty框架
不选择Java原生NIO编程的原因
1.NIO的类库和API繁杂,使用麻烦;
2.需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程;
3.可靠性能力补齐,工作量和难度都非常大;
为什么选择Netty框架
Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架Avro就使用了Netty作为底层通信框架,其他还有业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。
3 Netty线程模型设计思想
创建一个Netty项目
Maven依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
使用netty创建服务器端的时候,采用两个线程池
boss线程池 负责接收请求
work线程池 处理请求读写操作
4 基于Netty创建服务器端
Netty服务器端
public class ServerHandler extends SimpleChannelInboundHandler {
/**
* 获取数据
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
String request = byteBuf.toString(CharsetUtil.UTF_8);
System.out.println("request:" + request);
// 相应代码
ctx.writeAndFlush(Unpooled.copiedBuffer("平均月薪突破3w", CharsetUtil.UTF_8));
}
}
public class NettyServer {
private static int inetPort = 8080;
public static void main(String[] args) {
// 使用netty创建服务器端的时候,采用两个线程池
// boss线程池 负责接收请求
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// work线程池 处理请求读写操作
NioEventLoopGroup workGroup = new NioEventLoopGroup();
// 创建serverBootstrap
ServerBootstrap serverBootstrap = new ServerBootstrap();
// serverSocketChannel管理多个socketChannel
// NioServerSocketChannel标记当前为服务器端
serverBootstrap.group(workGroup, workGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
// 处理每个请求handler
sc.pipeline().addLast(new ServerHandler());
}
});
try {
// 绑定端口号
ChannelFuture channelFuture = serverBootstrap.bind(inetPort).sync();
System.out.println("服务器端启动成功:" + inetPort);
// 等待监听请求
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
Socket客户端
public class SocketClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("请问每特教育平均月薪突破多少?".getBytes());
// 长连接防止Server端报错
while(true){}
// outputStream.close();
// socket.close();
}
}
测试结果:
5 基于Netty创建客户端
Netty客户端
public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 活跃通道可以发送消息
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 发送数据
ctx.writeAndFlush(Unpooled.copiedBuffer("每特教育平均月薪突破多少?", CharsetUtil.UTF_8));
}
/**
* 读取消息
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
String resp = byteBuf.toString(CharsetUtil.UTF_8);
System.out.println("客户端获取服务器端相应:" + resp);
}
}
public class NettyClient {
public static void main(String[] args) {
//创建nioEventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler());
}
});
try {
// 发起同步连接
ChannelFuture sync = bootstrap.connect().sync();
sync.channel().closeFuture().sync();
} catch (Exception e) {
} finally {
group.shutdownGracefully();
}
}
}
测试结果:
6 什么是粘包、拆包基本概念
什么是粘包、拆包
粘包:多次发送的消息,客户端一次合并读取 msg+msg
Msg Msg=msgmsg
拆包:第一次完整消息+第二次部分消息组合、第二次剩下部分的消息
Msg Msg=MsgM sg
原因的造成:
因为现在的tcp连接默认以长连接的形式实现通讯,发送请求之后不会立马关闭连接。
客户端与服务器端建立连接,客户端发送一条消息,客户端与服务器端关闭连接,不存在粘包拆包问题;
客户端与服务器端建立连接,客户端发送多条消息,客户端与服务器端关闭连接,可能存在粘包拆包问题;
7 解决tcp协议粘包拆分思路
为什么会造成粘包与拆包? Tcp和缓冲区造成
Tcp协议为了能够高性能传输,发送和接收采用缓冲区
1.当客户端发送的数据消息<服务器端读取的缓冲区大小,会发生粘包;
2.当客户端发送的数据消息>服务器端读取的缓冲区大小,会发送拆包;
3.服务端不能够及时的获取缓冲区的数据,也会产生粘包的问题;
解决粘和拆包思路:
1.以固定的长度发送数据到缓冲区
2.可以在数据之间设置边界 \n
8 使用LineBaseDFrameDecoder解决粘包
利用编码器LineBaseDFrameDecoder解决tcp粘包的问题
serverBootstrap.group(workGroup, workGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
// 处理每个请求handler
// 对发送数据设置边界
sc.pipeline().addLast(new LineBasedFrameDecoder(1024));
sc.pipeline().addLast(new StringEncoder());
sc.pipeline().addLast(new ServerHandler());
}
});
对应写的语句加上\n或者\r\n
测试结果: