Android简单使用netty及谷歌的protobuf

文章主要内容

  1. netty TCP NIO服务端、客户端简单使用
  2. netty UDP NIO使用
  3. 谷歌protobuf使用
  4. netty 结合谷歌protobuf使用
  5. 只是netty protobuf的使用,更适合Android开发人员看,原理那些没有

protobuf简介

protobuf 是 Google 用于序列化结构化数据的语言,语言中立、平台中立、可扩展机制——相对于 XML,更小、更快、更简单。您只需定义一次数据的结构化方式,然后就可以使用特殊生成的源代码轻松地将结构化数据写入和读取各种数据流,并使用各种语言。(翻译的,自己去官网看)
个人理解是用于数据压缩,减少IO数据传输量。
protobuf 官网
protobuf github
protobuf java编写文档

protobuf使用

  1. 编写.proto结尾的文件,如:SocketData.proto,用于生成Java类。
// 真正的实体类,扩展数据为了通用准备
// required 必需的
// optional 可选
message SocketData{
  required int32 type = 1;// int 类型,可以根据这个来处理不同的消息
  optional bytes  data = 2; // byte[] 真正的数据
  optional string ext_s = 3;// String 扩展数据
  optional int64  ext_l = 4;// long 扩展数据
  optional float  ext_f = 5;// float 扩展数据
  optional string ext_s1 = 6;// String 扩展数据
  optional int64  ext_l1 = 7;// long 扩展数据
  optional double  ext_d = 8;// double 扩展数据
  optional double  ext_d1 = 9;// double 扩展数据
}
  1. 下载protobuf代码生成器,找到系统对应的版本进行下载,解压
    protobuf代码生成器下载地址
    protobuf代码生成器.png
  2. 生成java代码
  • 解压protobuf代码生成器,将编写好的SocketData.proto文件复制到bin目录下
  • 在cmd执行命令: protoc --java_out=lite:./ SocketData.proto
  • 将生成的代码复制到项目,即可使用


    proto生成java文件.png
  • protoc -h 查看更多编译内容

netty简介

Netty 官网
netty github
Netty 是一个 NIO 客户端服务器框架,可以快速轻松地开发协议服务器和客户端等网络应用程序。它极大地简化和流线了网络编程,例如 TCP 和 UDP 套接字服务器。(翻译的,自己去官网看)
个人理解,Netty是对java BIO NIO使用的统一封装,便于快速编写服务端和客户端的网络应用程序。

netty使用

netty官方事例

android项目 引入 netty protobuf

android {
    // ...
    // 解决引入报错
    packagingOptions {
        exclude 'META-INF/*******'
        exclude 'META-INF/INDEX.LIST'
        exclude 'META-INF/io.netty.versions.properties'
    }
}
dependencies {
    // netty
    implementation 'io.netty:netty-transport:4.1.79.Final' // 传输相关
    implementation 'io.netty:netty-codec:4.1.79.Final' // 编解码相关
    // 谷歌protobuf
    implementation 'com.google.protobuf:protobuf-javalite:3.21.2'
}
  • 如果有需要可以引入 'io.netty:netty-all:4.1.79.Final' 包含所有

netty protobuf tcp 服务端

  • EchoServer 服务端代码
/**
 * Netty nio 服务端
 */
public final class EchoServer {
    // 启动的端口号
    public static int PORT = 10303;
    private static final String tag = "TAG";
    // 启动的标志
    private AtomicBoolean isStart = new AtomicBoolean(false);
    // 管道:主要是读写消息
    private Channel channel;
    // 处理接收到的连接、消息等
    private EchoServerHandler serverHandler;
    // 消息接收回调
    private OnReceiveListener onReceiveListener;
    // 线程数,如果是0,Netty底层会根据CPU核心数*2进行创建
    private int threads;

    public EchoServer(int threads) {
        this.threads = threads;
    }

    public EchoServer() {
        this(0);
    }

    /**
     * 启动服务
     */
    public void start() {
        // 防止并发启动
        if (isStart.compareAndSet(false, true)) {
            // 使用线程池启动
            Executors.newSingleThreadExecutor().execute(() -> doStart());
        }

    }

    /**
     * 执行启动服务的真正代码
     */
    private void doStart() {
        // 主要处理Accept请求
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 处理其它 Connect Read Write
        EventLoopGroup workerGroup = new NioEventLoopGroup(threads);
    // 处理链接及消息
        serverHandler = new EchoServerHandler();
        serverHandler.setOnReceiveListener(onReceiveListener);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 64)
                    .option(ChannelOption.SO_REUSEADDR, true)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            // protobuf 解码
                            p.addLast(new ProtobufVarint32FrameDecoder());
                            p.addLast(new ProtobufDecoder(SocketData.getDefaultInstance()));
                            // protobuf 编码
                            p.addLast(new ProtobufVarint32LengthFieldPrepender());
                            p.addLast(new ProtobufEncoder());
                            p.addLast(serverHandler);
                        }
                    });

            // 绑定端口号
            ChannelFuture f = b.bind(PORT).sync();

            // 拿到Channel
            channel = f.channel();

            // 阻塞等待关闭
            channel.closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭所有事件循环以终止所有线程
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            // 关闭Channel
            close();
        }

    }

    /**
     * 判断Channel是否打开
     */
    private boolean isOpen() {
        return channel != null && channel.isOpen();
    }

    /**
     * 关闭及回收
     */
    public void close() {
        if (isStart.compareAndSet(true, false)) {
            if (isOpen()) {
                channel.close();
            }
            channel = null;
            serverHandler = null;
        }
    }

    /**
     * 群发消息,所有客户端会收到
     */
    public void send(SocketData packet) {
        if (isOpen()) {
            serverHandler.send(packet);
        }
    }
    
    // 消息回调
    public void setOnReceiveListener(OnReceiveListener listener) {
        this.onReceiveListener = listener;
    }
}
  • EchoServerHandler 服务端消息处理
/**
 * 服务端消息处理,处理客户端连接及发过来的消息
 */
@Sharable
public class EchoServerHandler extends SimpleChannelInboundHandler {
    // 接收到客户端消息回调
    private OnReceiveListener listener;
    // 保存所有客户端的连接
    private ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, SocketData msg) throws Exception {
        if (listener != null) {
            listener.onReceive(msg, ctx.channel());
        }
    }
    
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        group.add(ctx.channel()); 
    }

    /**
     * 群发消息
     */
    public void send(Object packet) {
        // 群发消息
        group.writeAndFlush(packet);
    }


    public void setOnReceiveListener(OnReceiveListener listener) {
        this.listener = listener;
    }
}

start方法是启动服务、close是关闭、send是群发消息,其它如果需要,自己编写即可。

netty protobuf tcp 客户端

  • EchoClient 客户端
/**
 * netty nio 客户端
 */
public final class EchoClient {
    // 启动的标志
    private AtomicBoolean isStart = new AtomicBoolean(false);
    // 服务端地址
    private InetAddress address;
    // 服务端端口号
    private int port;
    // 管道:主要是读写消息
    private Channel channel;
    // 消息接收回调
    private OnReceiveListener onReceiveListener;

    /**
     * 连接服务端端
     *
     * @param address 服务端地址
     * @param port    服务端端口号
     */
    public void start(InetAddress address, int port) {
        if (address == null) {
            return;
        }
        this.address = address;
        this.port = port;
        if (isStart.compareAndSet(false, true)) {
            Executors.newSingleThreadExecutor().execute(() -> doStart());
        }
    }

    private void doStart() {
        // 处理所有的事件
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 处理服务端发过来的消息。
            final EchoClientHandler clientHandler = new EchoClientHandler();
            // 处理服务端发过来的消息回调
            clientHandler.setOnReceiveListener(onReceiveListener);
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_REUSEADDR, true)// 地址复用
                    .option(ChannelOption.SO_KEEPALIVE, true)// 保持连接
                    .option(ChannelOption.TCP_NODELAY, true)// 不缓迟
                    .handler(new ChannelInitializer() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            // protobuf 解码
                            p.addLast(new ProtobufVarint32FrameDecoder());
                            p.addLast(new ProtobufDecoder(SocketData.getDefaultInstance()));
                            // protobuf 编码
                            p.addLast(new ProtobufVarint32LengthFieldPrepender());
                            p.addLast(new ProtobufEncoder());
                            // 自己的Handler
                            p.addLast(clientHandler);
                        }
                    });

            // 连接服务端
            ChannelFuture f = b.connect(address, port).sync();
            // 拿到Channel
            channel = f.channel();
            // 阻塞等待连接关闭
            channel.closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
            close();
        }
    }

    /**
     * 判断Channel是否打开
     */
    public boolean isOpen() {
        return channel != null && channel.isOpen();
    }

    /**
     * 关闭及回收
     */
    public void close() {
        if (isStart.compareAndSet(true, false)) {
            if (onStateListener != null) {
                onStateListener.onClose(channel);
            }
            if (isOpen()) {
                channel.close();
            }
            channel = null;
            address = null;
        }
    }

    /**
     * 向服务端发发送消息
     */
    public void send(SocketData packet) {
        if (isOpen()) {
            channel.writeAndFlush(packet);
        }
    }

    /**
     * 设置消息回调
     */
    public void setOnReceiveListener(OnReceiveListener onReceiveListener) {
        this.onReceiveListener = onReceiveListener;
    }
}
  • EchoClientHandler 客户端消息处理
/**
 * 处理客户端消息
 */
public class EchoClientHandler extends SimpleChannelInboundHandler {
    // 消息回调
    private OnReceiveListener onReceiveListener;

    /**
     * 接收到服务端的消息
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, SocketData msg) throws Exception {
        if (onReceiveListener != null) {
            onReceiveListener.onReceive(msg, ctx.channel());
        }
    }

    // 设置消息回调
    public void setOnReceiveListener(OnReceiveListener onReceiveListener) {
        this.onReceiveListener = onReceiveListener;
    }
}

netty protobuf udp 代码

  • NioUdpServer UDP启动代码
/**
 * netty UDP代码
 */
public class NioUdpServer {
    public static final int TYPE_IN = 1000; // 创建服务,发个创建广播
    public static final int TYPE_OUT = 1001; // 退出服务,发个退出广播
    public static final int TYPE_REC = 1002; // 这个是发个接收到消息的广播

    public static int PORT = 10505;
    // 管道:主要是读写操作
    private Channel channel;
    // 消息回调
    private OnReceiveListener onReceiveListener;
    // 消息处理
    private NioUdpServerHandler serverHandler;
    private Handler handler = new Handler(Looper.getMainLooper());

    public void start() {
        if (!isOpen()) {
            Executors.newSingleThreadExecutor()
                    .execute(() -> doStart());
        }
    }

    public void setOnReceiveListener(OnReceiveListener onReceiveListener) {
        this.onReceiveListener = onReceiveListener;
    }

    private void doStart() {
        // 处理事件
        EventLoopGroup group = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap();
            // 消息处理
            serverHandler = new NioUdpServerHandler(this);
            // 消息回调设置
            serverHandler.setOnReceiveListener(onReceiveListener);
            bootstrap.group(group)
                    .channel(NioDatagramChannel.class) // udp设置
                    .option(ChannelOption.SO_BROADCAST, true)// 广播设置
                    .option(ChannelOption.SO_REUSEADDR, true)
                    .handler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(DatagramChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast(serverHandler);
                        }
                    });
            // 绑定端口
            ChannelFuture future = bootstrap.bind(PORT).sync();
            // 拿到Channel
            channel = future.channel();
            // 发一个启动广播
            sendBroadcast(TYPE_IN);
            // 阻塞等待关闭
            channel.closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("TAG", "udp server error " + e.getMessage());
        } finally {
            close();
            // 关闭
            group.shutdownGracefully();
        }

    }

    /**
     * 发送广播
     */
    public void sendBroadcast(int type) {
        sendBroadcast(SocketData.newBuilder()
                .setType(type)
                .setExtS(Build.DEVICE)
                .build());
    }

    /**
     * 发送广播
     */
    public void sendBroadcast(SocketData data) {
        if (isOpen()) {
            // 发送udp包
            DatagramPacket packet = new DatagramPacket(Unpooled.copiedBuffer(data.toByteArray()),
                    new InetSocketAddress("255.255.255.255", PORT));
            channel.writeAndFlush(packet);
        }
    }

    /**
     * 是否打开
     */
    private boolean isOpen() {
        return channel != null && channel.isOpen();
    }

    /**
     * 关闭回收
     */
    public void close() {
        if (isOpen()) {
            // 发退出广播 udp容易丢包,发2次没啥大问题
            sendBroadcast(TYPE_OUT);
            // 发退出广播
            handler.postDelayed(() -> sendBroadcast(TYPE_OUT), 100);

            // 真正退出
            handler.postDelayed(() -> {
                channel.close();
                channel = null;
            }, 300);

        }
    }
}
  • NioUdpServer UDP消息处理代码
/**
 * 这个是适配器
 */
public class NioUdpServerHandler extends SimpleChannelInboundHandler {

    private OnReceiveListener onReceiveListener;


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) {
        try {
            // 发送端的信息
            InetSocketAddress sender = msg.sender();
            String ip = sender.getHostName();
            // 解析数据
            ByteBuf buf = msg.content();
            byte[] data = new byte[buf.readableBytes()];
            buf.readBytes(data);
            // 数据转换成 protobuf  SocketData
            SocketData socketData = SocketData.parseFrom(data);
            int type = socketData.getType();
            String name = socketData.getExtS();
            // 消息回调
            if (onReceiveListener != null) {
                onReceiveListener.onReceive(socketData, ctx.channel());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void setOnReceiveListener(OnReceiveListener onReceiveListener) {
        this.onReceiveListener = onReceiveListener;
    }

}

只是写了广播消息(可以获取到同一路由下的其它设置的IP信息),具体向哪个服务端发消息,其实也一样。

总结

android使用Netty的应用场景

  • wifi 局域网下,数据传输(文件 音视频数据等)
  • wifi p2p,数据传输(文件 音视频数据等)

你可能感兴趣的:(Android简单使用netty及谷歌的protobuf)