天天看点

闭关修炼(十三)Netty5.0Netty5粘包与拆包Netty 5.0架构剖析和源码解读.pdf

文章目录

  • Netty5
    • jar包
    • 服务端
    • 客户端
    • 长连接与短连接
  • 粘包与拆包
    • 什么是粘包和拆包
    • 拆包方法
  • Netty 5.0架构剖析和源码解读.pdf

Netty5

Netty有很多版本,在4的时候就有巨大的革命,API基本都改了。

官方说5不安全。开发中用Netty4比较多

TCP粘包、拆包用netty3代码特别复杂,netty4只用分隔符进行区分就行了,比较容易一些。

jar包

<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0.Alpha2</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.marshalling</groupId>
            <artifactId>jboss-marshalling</artifactId>
            <version>1.3.19.GA</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.marshalling</groupId>
            <artifactId>jboss-marshalling-serial</artifactId>
            <version>1.3.18.GA</version>
            <scope>test</scope>
        </dependency>
           

服务端

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;

class ServerHandler extends ChannelHandlerAdapter {
    // 当通道被调用时会执行的方法(拿到数据)
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);
        // 客户端的msg
        String value = (String)msg;
        System.out.println("服务器收到客户端消息:" + value);
        // 回复客户端, 加入到缓冲区并发送
        ctx.writeAndFlush("服务器收到");
        // 上面writeAndFlush等同于先调用write再调用flush
    }
}
public class NettyServer {
    static int PORT = 8080;
    public static void main(String[] args) throws InterruptedException {
        System.out.println("服务端启动");
        // 创建两个线程池,一个负责接收客户端,一个进行传输
        NioEventLoopGroup pGroup = new NioEventLoopGroup();
        NioEventLoopGroup cGroup = new NioEventLoopGroup();
        // 创建辅助类
        ServerBootstrap sbs = new ServerBootstrap();
        sbs.group(pGroup, cGroup).channel(NioServerSocketChannel.class)
                // 设置缓冲区大小
                .option(ChannelOption.SO_BACKLOG, 1024)
                .option(ChannelOption.SO_SNDBUF, 32 * 1024)
                .option(ChannelOption.SO_RCVBUF, 32 * 1024)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        // 设置放回结果是string类型
                        socketChannel.pipeline().addLast(new StringDecoder());
                        socketChannel.pipeline().addLast(new ServerHandler());
                    }
                });
        ChannelFuture cf = sbs.bind(PORT).sync();
        // 关闭
        cf.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
        cGroup.shutdownGracefully();
    }
}

           

客户端

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
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.StringDecoder;

class ClientHandler extends ChannelHandlerAdapter{
    // 当通道被调用执行该方法
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);
        // 接收数据
        String data = (String) msg;
        System.out.println("client msg:" + data);
    }
}
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("客户端启动");
        NioEventLoopGroup pGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(pGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new StringDecoder());
                        socketChannel.pipeline().addLast(new ClientHandler());

                    }
                });
        ChannelFuture cf = bootstrap.connect("127.0.0.1", NettyServer.PORT).sync();
        // 发送两次请求,自动设置了粘包
        cf.channel().writeAndFlush(Unpooled.wrappedBuffer("测试消息~".getBytes()));
        cf.channel().writeAndFlush(Unpooled.wrappedBuffer("测试消息~".getBytes()));
        cf.channel().writeAndFlush(Unpooled.wrappedBuffer("测试消息~".getBytes()));
        Thread.sleep(1000);
        cf.channel().writeAndFlush(Unpooled.wrappedBuffer("测试消息~".getBytes()));
        cf.channel().writeAndFlush(Unpooled.wrappedBuffer("测试消息~".getBytes()));

        // 等待客户端端口号关闭
        cf.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
    }
}

           

长连接与短连接

关闭客户端,服务器会报IOException 远程主机强迫关闭了一个现有的连接,是个是因为Netty5连接是长连接,没有4次挥手直接关闭了客户端。

长连接常用在移动端消息推送和MQ,短连接用于HTTP协议

粘包与拆包

什么是粘包和拆包

TCP中有这个概念,粘包是将多个包合在一起,拆包是将一个包拆分多个包

在Netty5中已经帮我们做了优化传输,减轻服务器传输压力。

拆包方法

消息定长,报文大小固定长度,不够空格补全,发送和接收放遵守相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分

在客户端和服务端initChannel中添加:

另一种解决办法是包尾添加特殊分隔符,例如每条报文结束都添加回车换行符或者指定特殊字符作为报文分隔符,接收方通过分隔符切分报文区分,如果客户端writeAndFlush发送数据不包含分隔符,通道会被阻塞,不会发送包,直到writeAndFlush写入通道的内容有分隔符,之前的数据一起被发送。

在客户端和服务端initChannel中添加下面代码,表示用_&进行分割

ByteBuf buf = Unpooled.copiedBuffer("_&".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
           

还有一种是定义消息头和消息体,消息头包含信息的总长度,不会,略。

Netty 5.0架构剖析和源码解读.pdf

https://pan.baidu.com/s/1mg4rN7M