文章目录
Netty入门案例
Server端
package com.wy.netty.simple;
import com.wy.netty.simple.messagepack.MsgPackEncoder;
import com.wy.netty.simple.messagepack.MsgpackDecoder;
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.sctp.nio.NioSctpServerChannel;
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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
* @ClassName NettyServer
* @Description TODO
* @Author Wang Yue
* @Date 2021/2/15 18:02
*/
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//两个group的
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
//使用固定长度的解码器
// ch.pipeline().addLast(new FixedLengthFrameDecoder(20));
//使用自定义符合进行分割
ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
//使用换行符进行分割
// ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("...服务器 is ready...");
//启动服务器
ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.wy.netty.simple;
import com.wy.netty.simple.pojo.User;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
import org.msgpack.MessagePack;
import org.msgpack.template.Templates;
/**
* @ClassName NettyServerHandler
* @Description TODO
* @Author Wang Yue
* @Date 2021/2/15 18:19
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
private int count = 1;
//读取数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String buf = (String) msg;
System.out.println("客户端发送消息是: " + buf + " 本次是第:" + count++ + "次");
ctx.writeAndFlush(Unpooled.copiedBuffer(buf + "$_", CharsetUtil.UTF_8));
}
//处理异常,一般需要关闭通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
Client端
package com.wy.netty.simple;
import com.wy.netty.simple.messagepack.MsgPackEncoder;
import com.wy.netty.simple.messagepack.MsgpackDecoder;
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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
* @ClassName NettyClient
* @Description TODO
* @Author Wang Yue
* @Date 2021/2/15 18:41
*/
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)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
//使用自定义符合进行分割
ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
//使用换行符
// ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
//加入自己的处理器
ch.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("...客户端 is ok...");
//启动客户端连接服务器
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
//给关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
package com.wy.netty.simple;
import com.wy.netty.simple.pojo.User;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* @ClassName NettyClientHandler
* @Description TODO
* @Author Wang Yue
* @Date 2021/2/15 18:50
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
private int count = 1;
//当通道就绪时,就会触发该方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 100; i++) {
ctx.writeAndFlush(Unpooled.copiedBuffer("hello ,server" + "$_", CharsetUtil.UTF_8));
}
}
//当通道有读取事件时,会触发此方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String buf = (String) msg;
System.out.println("服务器回复消息: " + buf + " 本次是第:" + count++ + "次");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
TCP粘包/拆包问题
TCP是个"流"协议,所谓流,就是没有界限的一串数据。可以想象河里的水流,他们是连成一片,没有界限的。TCP底层并不清楚业务数据的含义,它会根据缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP分为多个包发送,也可能把多个小包封装成一个大包发送。
粘包/拆包产生的原因
- 应用程序write写入的字节大小大于套接口发送缓冲区的大小
- 进行MSS(最大报文程度)大小的TCP分段
- 以太网帧的payload大于MTU进行IP分片
粘包/拆包的解决策略
- 消息定长,比如按照每个报文200字节,不够补空格的方式
- 在包尾增加回车换行符进行分割,例如FTP协议
- 将消息分为消息头和消息体,消息头中包含表示消息总长度或者消息体长度的字段。通常的设计思路为消息头的第一个字段使用int32来表示消息的总程度
- 使用更复杂的应用层协议
Netty解决粘包/拆包问题
LineBasedFrameDecoder和StringDecoder
在入门案例中已经有如下代码。
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
使用LineBasedFrameDecoder
和StringDecoder
可以解决粘包/拆包问题。
首先解释LineBasedFrameDecoder
:是以换行符未结束标志的解码器,它的工作原理是一次遍历ByteBuf中的可读字节,判断是否有"\n"或者"\r\n",如果有,就以此位置为结束位置。并且可以设置最大长度,笔者设置为1024,若超出最大长度后仍然没有发现换行符,就会抛出异常。
StringDecoder
:会将接受到的对象装换为字符串,然后继续调用后面的Handler。
分隔符解码器
Netty还提供了分隔符解码器
DelimiterBasedFrameDecoder
,它可以支持使用自定义字符,进行报文分割。
使用实例如下
ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
定长解码器
Netty提供定长解码器
FixedLengthFrameDecoder
可以完成对定长消息的解码
使用实例如下
//使用固定长度的解码器
ch.pipeline().addLast(new FixedLengthFrameDecoder(20));