客户端示例代码:
/**
* @program: learnnetty
* @description: 客户端
* @create: 2020-05-09 13:49
**/
public class Client {
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(worker)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel
.pipeline()
.addLast(new ClientHandler());
}
});
bootstrap.connect("127.0.0.1", 8080);
}
}
客户端处理器示例代码:
/**
* @program: learnnetty
* @description: 处理类
* @create: 2020-05-09 13:53
**/
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 1000; i++) {
ByteBuf buf = getByteBuf(ctx);
ctx.channel().writeAndFlush(buf);
buf.release();
}
}
private ByteBuf getByteBuf(ChannelHandlerContext ctx){
byte[] bytes = "这是一条客户端发往服务端的消息".getBytes(StandardCharsets.UTF_8);
return ctx.alloc().buffer().writeBytes(bytes);
}
}
服务端示例代码:
/**
* @program: learnnetty
* @description: 服务端
* @create: 2020-05-09 13:49
**/
public class Server {
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
NioEventLoopGroup boss = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel
.pipeline()
.addLast(new ServerHandler());
}
});
serverBootstrap.bind(8080);
}
}
服务端处理器示例代码:
/**
* @program: learnnetty
* @description: 处理类
* @create: 2020-05-09 13:54
**/
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf)msg;
System.out.println(new Date() + ",服务端收到:" + byteBuf.toString(StandardCharsets.UTF_8));
}
}
出现问题的输出:
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 14:18:42 CST 2020,服务端收到:这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一�
Sat May 09 14:18:42 CST 2020,服务端收到:�客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的消息这是一条客户端发往服务端的�
可以从控制台打印的数据看出:
在应用层使用Netty框架来发送数据,Netty的底层则是以ByteBuf为单位发送数据包,但是操作系统的底层则是按照字节流来发送数据包,因此当数据到了服务端是以字节流方式读入,然后到Netty应用层重新拼装成ByteBuf,而此处的ByteBuf与客户端按照顺序发送的ByteBuf可能是不对等的。
如果没有Netty,用户需要自己实现拆包,基本的思路的就是不断从TCP缓冲区读取数据,每次读完数据都需要判断一下是否是完整的数据包:
Netty自带的拆包器主要有以下四种:
名称 | 说明 |
---|---|
FixedLengthFrameDecoder | 基于长度进行分包,适用于每包长度固定且协议简单的情况; |
LineBasedFrameDecoder | 基于分割符进行拆包,即在发送数据包时,每包之间都是用换行符间隔; |
DelimiterBasedFrameDecoder | 基于自定义分隔符进行拆包,即在发送数据时是用特定的符号进行分割; |
LengthFieldBasedFrameDecoder | 基于长度域进行拆包,是用此拆包器需要在协议中规定长度域字段,拆包器会根据长度域内规定的长度进行拆包; |
声明一个数据包实体类:
/**
* @program: learnnetty
* @description: 数据包实体
* @create: 2020-05-09 17:09
**/
public class Frame {
private byte flag;
private int length;
private String content;
public byte getFlag() {
return flag;
}
public void setFlag(byte flag) {
this.flag = flag;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Frame{" +
"flag=" + flag +
", length=" + length +
", content='" + content + '\'' +
'}';
}
}
此处的长度应该是转为byte数组之后的长度。
解码器:
/**
* @program: learnnetty
* @description: 解码
* @create: 2020-05-09 17:25
**/
public class FrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
//跳过标志位
byteBuf.skipBytes(4);
//读取数据域长度位
int length = byteBuf.readInt();
//读取数据
byte[] data = new byte[length];
byteBuf.readBytes(data);
//生成实体
Frame frame = new Frame();
frame.setLength(length);
frame.setContent(new String(data, StandardCharsets.UTF_8));
list.add(frame);
}
}
编码器:
/**
* @program: learnnetty
* @description: 编码
* @create: 2020-05-09 17:25
**/
public class FrameEncoder extends MessageToByteEncoder<Frame> {
private static final int HEAD = 0x990718;
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Frame frame, ByteBuf byteBuf) throws Exception {
byteBuf.writeInt(HEAD);
byteBuf.writeInt(frame.getLength());
byteBuf.writeBytes(frame.getContent().getBytes(StandardCharsets.UTF_8));
}
}
客户端:
/**
* @program: learnnetty
* @description: 客户端
* @create: 2020-05-09 13:49
**/
public class Client {
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(worker)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel
.pipeline()
.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 4, 4))
.addLast(new FrameDecoder())
.addLast(new ClientHandler())
.addLast(new FrameEncoder());
}
});
bootstrap.connect("127.0.0.1", 8080);
}
}
服务端:
/**
* @program: learnnetty
* @description: 服务端
* @create: 2020-05-09 13:49
**/
public class Server {
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
NioEventLoopGroup boss = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel
.pipeline()
.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 4, 4))
.addLast(new FrameDecoder())
.addLast(new ServerHandler())
.addLast(new FrameEncoder());
}
});
serverBootstrap.bind(8080);
}
}
客户端处理器:
/**
* @program: learnnetty
* @description: 处理类
* @create: 2020-05-09 13:53
**/
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 1000; i++) {
Frame frame = new Frame();
frame.setContent("这是一条客户端发往服务端的消息");
frame.setLength(frame.getContent().getBytes(StandardCharsets.UTF_8).length);
ctx.channel().writeAndFlush(frame);
}
}
}
服务端处理器:
/**
* @program: learnnetty
* @description: 处理类
* @create: 2020-05-09 13:54
**/
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Frame frame = (Frame)msg;
System.out.println(new Date() + ",服务端收到:" + frame.getContent());
}
}
服务端输出(部分):
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:19:26 CST 2020,服务端收到:这是一条客户端发往服务端的消息
可以看到,输出中并没有出现拆包和粘包的现象。
添加发送其他头部报文的客户端:
/**
* @program: learnnetty
* @description: 编码
* @create: 2020-05-09 17:25
**/
public class FrameEncoder extends MessageToByteEncoder<Frame> {
private static final int HEAD = 0x990718;
private static final int FAKE_HEAD = 0x123456;
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Frame frame, ByteBuf byteBuf) throws Exception {
Random random = new Random();
if (random.nextInt(11) > 6){
byteBuf.writeInt(FAKE_HEAD);
String fakeMsg = "这是虚假的信息";
byteBuf.writeInt(fakeMsg.getBytes(StandardCharsets.UTF_8).length);
byteBuf.writeBytes(fakeMsg.getBytes(StandardCharsets.UTF_8));
}else {
byteBuf.writeInt(HEAD);
byteBuf.writeInt(frame.getLength());
byteBuf.writeBytes(frame.getContent().getBytes(StandardCharsets.UTF_8));
}
}
}
服务端输出:
Sat May 09 18:29:59 CST 2020,服务端收到:这是虚假的信息
Sat May 09 18:29:59 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:29:59 CST 2020,服务端收到:这是虚假的信息
Sat May 09 18:29:59 CST 2020,服务端收到:这是虚假的信息
Sat May 09 18:29:59 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:29:59 CST 2020,服务端收到:这是一条客户端发往服务端的消息
可以看到有许多非法报文被读取了。
创建自定义的LengthFieldBasedFrameDecoder:
/**
* @program: learnnetty
* @description: 拆包器
* @create: 2020-05-09 18:31
**/
public class Spliter extends LengthFieldBasedFrameDecoder {
public Spliter(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
//检查头部标志位
if (in.getInt(in.readerIndex()) != FrameEncoder.HEAD){
//如果收到不正确的数据包就断连
ctx.channel().close();
return null;
}
return super.decode(ctx, in);
}
}
使用自定义的LengthFieldBasedFrameDecoder:
/**
* @program: learnnetty
* @description: 服务端
* @create: 2020-05-09 13:49
**/
public class Server {
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
NioEventLoopGroup boss = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel
.pipeline()
.addLast(new Spliter(Integer.MAX_VALUE, 4, 4))
.addLast(new FrameDecoder())
.addLast(new ServerHandler())
.addLast(new FrameEncoder());
}
});
serverBootstrap.bind(8080);
}
}
服务端输出:
Sat May 09 18:35:12 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:35:12 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:35:12 CST 2020,服务端收到:这是一条客户端发往服务端的消息
Sat May 09 18:35:12 CST 2020,服务端收到:这是一条客户端发往服务端的消息
当前能做到的仅仅是收到错误报文后断连。