Netty使用Google Protobuf进行编解码

2023-05-16

文章目录

  • 一、概述
    • 1、编解码基础
    • 2、Netty编解码器
    • 3、Protobuf概述
  • 二、Protobuf基本使用
    • 1、引入jar包
    • 2、下载Protobuf
    • 3、编写Student.proto
    • 4、生成StudentPOJO类
    • 5、服务器端
    • 6、客户端
    • 7、验证一下吧
  • 三、Netty使用Protobuf发送多类型对象
    • 1、编写Student.proto
    • 2、生成MyDataInfo.java
    • 3、服务端
    • 4、客户端
    • 5、测试一下吧

一、概述

1、编解码基础

编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据0时就需要解码。

通常来说,codec(编解码器)的组成部分有两个: decoder(解码器)和 encoder(编码器)。encoder 负责把业务数据转换成字节2)码数据,decoder 负责把字节码数据转换成业务数据。

在这里插入图片描述

2、Netty编解码器

Netty自身也携带了一些codec(编解码器):

StringDecoder // 对字符串数据进行解码
StringEncoder // 对字符串数据进行编码
ObjectDecoder // 对Java对象进行解码
ObjectEncoder // 对Java对象进行编码
... 

其中,Netty自带的ObjectDecoder 和ObjectEncoder 可以实现对POJO对象或各种业务对象的编码和解码,但是底层使用的仍是Java序列化技术,而Java序列化技术效率不高并且无法跨语言,所以Google的Protobuf是当今最火热的编解码器。

3、Protobuf概述

Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC[远程过程调用 remote procedurecall] 数据交换格式。

参考文档:https://developers.google.com/protocol-buffers/docs/proto

Protobug是以message的方式来管理数据的,支持跨平台、跨语言,即 [ 客户端和服务器端可以是不同语言编写的 ] (支持目前绝大多数语言,例如C++、C#、Java、Python、Go等),性能高,可靠性 高。

二、Protobuf基本使用

1、引入jar包

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.6.1</version>
</dependency>

2、下载Protobuf

https://github.com/protocolbuffers/protobuf/releases

我这里使用的是3.6.1版本,要找到对应的版本:https://github.com/protocolbuffers/protobuf/releases?page=11
在这里插入图片描述

3、编写Student.proto

syntax = "proto3"; //版本
option java_outer_classname = "StudentPOJO";//生成的外部类名,同时也是文件名
//protobuf 使用message 管理数据
message Student { //会在 StudentPOJO 外部类生成一个内部类 Student, 他是真正发送的POJO对象
    int32 id = 1; // Student 类中有 一个属性 名字为 id 类型为int32(protobuf类型) 1表示属性序号,不是值
    string name = 2;
    string msg = 3;
}

4、生成StudentPOJO类

(1)将Student.proto文件拷贝到protoc.exe同级目录并执行命令:

protoc-3.6.1-win32\bin>protoc.exe --java_out=. Student.proto

在这里插入图片描述
会生成一个StudentPOJO.java文件,该文件可以放在项目中使用了,一定不要修改该文件。

5、服务器端


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.protobuf.ProtobufDecoder;

public class NettyServer {
    public static void main(String[] args) throws Exception {


        //创建BossGroup 和 WorkerGroup
        //说明
        //1. 创建两个线程组 bossGroup 和 workerGroup
        //2. bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup完成
        //3. 两个都是无限循环
        //4. bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数
        //   默认实际 cpu核数 * 2
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //8



        try {
            //创建服务器端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();

            //使用链式编程来进行设置
            bootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用NioSocketChannel 作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
//                    .handler(null) // 该 handler对应 bossGroup , childHandler 对应 workerGroup
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象(匿名对象)
                        //给pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {


                            ChannelPipeline pipeline = ch.pipeline();
                            //在pipeline加入ProtoBufDecoder
                            //指定对哪种对象进行解码
                            pipeline.addLast("decoder", new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                            pipeline.addLast(new NettyServerHandler());
                        }
                    }); // 给我们的workerGroup 的 EventLoop 对应的管道设置处理器

            System.out.println(".....服务器 is ready...");

            //绑定一个端口并且同步, 生成了一个 ChannelFuture 对象
            //启动服务器(并绑定端口)
            ChannelFuture cf = bootstrap.bind(6668).sync();

            //给cf 注册监听器,监控我们关心的事件

            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("监听端口 6668 成功");
                    } else {
                        System.out.println("监听端口 6668 失败");
                    }
                }
            });


            //对关闭通道进行监听
            cf.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

}


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.util.CharsetUtil;

/*
说明
1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范)
2. 这时我们自定义一个Handler , 才能称为一个handler
 */
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public class NettyServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> {


    //读取数据实际(这里我们可以读取客户端发送的消息)
    /*
    1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
    2. Object msg: 就是客户端发送的数据 默认Object
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, StudentPOJO.Student msg) throws Exception {

        //读取从客户端发送的StudentPojo.Student


        System.out.println("客户端发送的数据 id=" + msg.getId() + " 名字=" + msg.getName() + "msg" + msg.getMsg());
    }



//    //读取数据实际(这里我们可以读取客户端发送的消息)
//    /*
//    1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
//    2. Object msg: 就是客户端发送的数据 默认Object
//     */
//    @Override
//    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//
//        //读取从客户端发送的StudentPojo.Student
//
//        StudentPOJO.Student student = (StudentPOJO.Student) msg;
//
//        System.out.println("客户端发送的数据 id=" + student.getId() + " 名字=" + student.getName());
//    }

    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        //writeAndFlush 是 write + flush
        //将数据写入到缓存,并刷新
        //一般讲,我们对这个发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端", CharsetUtil.UTF_8));
    }

    //处理异常, 一般是需要关闭通道

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

6、客户端


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.protobuf.ProtobufEncoder;

public class NettyClient {
    public static void main(String[] args) throws Exception {

        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();


        try {
            //创建客户端启动对象
            //注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
            Bootstrap bootstrap = new Bootstrap();

            //设置相关参数
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            //在pipeline中加入 ProtoBufEncoder
                            pipeline.addLast("encoder", new ProtobufEncoder());
                            pipeline.addLast(new NettyClientHandler()); //加入自己的处理器
                        }
                    });

            System.out.println("客户端 ok..");

            //启动客户端去连接服务器端
            //关于 ChannelFuture 要分析,涉及到netty的异步模型
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        }finally {

            group.shutdownGracefully();

        }
    }
}

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    //当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        //发生一个Student 对象到服务器

        StudentPOJO.Student student = StudentPOJO.Student.newBuilder()
                .setId(4)
                .setName("张三")
                .setMsg("我会武功,是个饭桶")
                .build();
        //Teacher , Member ,Message
        ctx.writeAndFlush(student);

    }

    //当通道有读取事件时,会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

7、验证一下吧

.....服务器 is ready...
监听端口 6668 成功
客户端发送的数据 id=4 名字=张三msg我会武功,是个饭桶
客户端 ok..
服务器回复的消息:hello, 客户端
服务器的地址: /127.0.0.1:6668

三、Netty使用Protobuf发送多类型对象

1、编写Student.proto

syntax = "proto3";
option optimize_for = SPEED; // 加快解析
// option java_package="com.demo.netty.codec";   //指定生成到哪个包下
option java_outer_classname="MyDataInfo"; // 外部类名, 文件名

//protobuf 可以使用message 管理其他的message
message MyMessage {

    //定义一个枚举类型
    enum DataType {
        StudentType = 0; //在proto3 要求enum的编号从0开始
        WorkerType = 1;
    }

    //用data_type 来标识传的是哪一个枚举类型
    DataType data_type = 1; // 编号

    //表示每次枚举类型最多只能出现其中的一个, 节省空间 ,dataBody可以自己命名
    oneof dataBody {
        Student student = 2; // 编号
        Worker worker = 3; // 编号
    }

}


message Student {
    int32 id = 1;//Student类的属性
    string name = 2; //
}
message Worker {
    string name=1;
    int32 age=2;
}

2、生成MyDataInfo.java

(1)将Student.proto文件拷贝到protoc.exe同级目录并执行命令:

protoc-3.6.1-win32\bin>protoc.exe --java_out=. Student.proto

在这里插入图片描述
生成的MyDataInfo.java可以拷贝到项目中使用了。

3、服务端


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.protobuf.ProtobufDecoder;

public class NettyServer {
    public static void main(String[] args) throws Exception {


        //创建BossGroup 和 WorkerGroup
        //说明
        //1. 创建两个线程组 bossGroup 和 workerGroup
        //2. bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup完成
        //3. 两个都是无限循环
        //4. bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数
        //   默认实际 cpu核数 * 2
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //8



        try {
            //创建服务器端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();

            //使用链式编程来进行设置
            bootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用NioSocketChannel 作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
//                    .handler(null) // 该 handler对应 bossGroup , childHandler 对应 workerGroup
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象(匿名对象)
                        //给pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {


                            ChannelPipeline pipeline = ch.pipeline();
                            //在pipeline加入ProtoBufDecoder
                            //指定对哪种对象进行解码
                            pipeline.addLast("decoder", new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
                            pipeline.addLast(new NettyServerHandler());
                        }
                    }); // 给我们的workerGroup 的 EventLoop 对应的管道设置处理器

            System.out.println(".....服务器 is ready...");

            //绑定一个端口并且同步, 生成了一个 ChannelFuture 对象
            //启动服务器(并绑定端口)
            ChannelFuture cf = bootstrap.bind(6668).sync();

            //给cf 注册监听器,监控我们关心的事件

            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("监听端口 6668 成功");
                    } else {
                        System.out.println("监听端口 6668 失败");
                    }
                }
            });


            //对关闭通道进行监听
            cf.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

}


import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

/*
说明
1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范)
2. 这时我们自定义一个Handler , 才能称为一个handler
 */
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {


    //读取数据实际(这里我们可以读取客户端发送的消息)
    /*
    1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
    2. Object msg: 就是客户端发送的数据 默认Object
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {

        //根据dataType 来显示不同的信息
        MyDataInfo.MyMessage.DataType dataType = msg.getDataType();
        if(dataType == MyDataInfo.MyMessage.DataType.StudentType) {

            MyDataInfo.Student student = msg.getStudent();
            System.out.println("学生id=" + student.getId() + " 学生名字=" + student.getName());

        } else if(dataType == MyDataInfo.MyMessage.DataType.WorkerType) {
            MyDataInfo.Worker worker = msg.getWorker();
            System.out.println("工人的名字=" + worker.getName() + " 年龄=" + worker.getAge());
        } else {
            System.out.println("传输的类型不正确");
        }


    }



//    //读取数据实际(这里我们可以读取客户端发送的消息)
//    /*
//    1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
//    2. Object msg: 就是客户端发送的数据 默认Object
//     */
//    @Override
//    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//
//        //读取从客户端发送的StudentPojo.Student
//
//        StudentPOJO.Student student = (StudentPOJO.Student) msg;
//
//        System.out.println("客户端发送的数据 id=" + student.getId() + " 名字=" + student.getName());
//    }

    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        //writeAndFlush 是 write + flush
        //将数据写入到缓存,并刷新
        //一般讲,我们对这个发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~", CharsetUtil.UTF_8));
    }

    //处理异常, 一般是需要关闭通道

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

4、客户端


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.protobuf.ProtobufEncoder;

public class NettyClient {
    public static void main(String[] args) throws Exception {

        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();


        try {
            //创建客户端启动对象
            //注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
            Bootstrap bootstrap = new Bootstrap();

            //设置相关参数
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            //在pipeline中加入 ProtoBufEncoder
                            pipeline.addLast("encoder", new ProtobufEncoder());
                            pipeline.addLast(new NettyClientHandler()); //加入自己的处理器
                        }
                    });

            System.out.println("客户端 ok..");

            //启动客户端去连接服务器端
            //关于 ChannelFuture 要分析,涉及到netty的异步模型
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        }finally {

            group.shutdownGracefully();

        }
    }
}


import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

import java.util.Random;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    //当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        //随机的发送Student 或者 Workder 对象
        int random = new Random().nextInt(3);
        MyDataInfo.MyMessage myMessage = null;

        if(0 == random) { //发送Student 对象

            myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.StudentType).setStudent(MyDataInfo.Student.newBuilder().setId(5).setName("张三").build()).build();
        } else { // 发送一个Worker 对象
            myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.WorkerType).setWorker(MyDataInfo.Worker.newBuilder().setAge(20).setName("李四").build()).build();
        }

        ctx.writeAndFlush(myMessage);
    }

    //当通道有读取事件时,会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

5、测试一下吧

.....服务器 is ready...
监听端口 6668 成功
工人的名字=李四 年龄=20
学生id=5 学生名字=张三
工人的名字=李四 年龄=20

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Netty使用Google Protobuf进行编解码 的相关文章

随机推荐

  • macOS中iOS模拟卡顿 ios simulator 高CPU占用解决办法

    问题描述 已经忘记从什么时间起 xff0c iOS的模拟器只要一开启 xff0c 我电脑的风扇就开始嚎叫了 我印象中以前开模拟器 xff0c 风扇还是比较安静的 开发iOS模拟器后 xff0c 会有一个Spotlight进程占用了大量的CP
  • 03[Tampermonkey开发]赖人必备自动点赞插件开发

    span class token comment 61 61 UserScript 61 61 span span class token comment 64 name bilibili auto like span span class
  • 04[Tampermonkey开发]无限三连之术(幻术)

    span class token comment 61 61 UserScript 61 61 span span class token comment 64 name bilibili max like span span class
  • macOS 安装istats zsh: command not found: istats

    现象 已经成功安装了istats xff0c 但是仍然提示zsh command not found istats span class token function sudo span gem span class token funct
  • “System Events”遇到一个错误:“osascript”不允许发送按键。 (1002)

    我想使用applescript模拟按键请求 xff0c tell application span class token string 34 System Events 34 span key code span class token
  • IDEA自动补全tab键向下选择s-tab向上选择

    友情提醒 目前不完美 xff0c 这样设置完后 tab键制表符功能会受到影响 目前没找到完美的解决办法 目前是使用其他的按键来代替tab的功能 34 代替tab inoremap span class token operator lt s
  • 20_[nvim0.5+从0单排]_lsp状态栏(lualine)标签页(bufferline)美化

    视频与目录 项目值教程目录https blog csdn net lxyoucan article details 120641546B站视频暂无 20 nvim0 5 43 从0单排 lsp状态栏标签页美化lualine bufferli
  • macOS平铺窗口yabai配置分享

    简介 yabai 是一个窗口管理实用程序 xff0c 旨在作为 macOS 内置窗口管理器的扩展工作 yabai 允许您使用直观的命令行界面自由控制窗口 空间和显示 xff0c 并可选择使用设置用户定义的键盘快捷键 skhd和其他第三方软件
  • Netty入门案例——Netty实现websocket

    文章目录 一 服务端二 网页 一 服务端 span class token keyword import span span class token namespace io span class token punctuation spa
  • React Navigation中使用typescript简洁演示代码

    最近在尝试转到typescript xff0c 之前代码中含有的大量 navigaiton any近期打算把这样的代码优化一下 参考以下文档 xff1a https reactnavigation org docs typescript 尽
  • Linux新磁盘挂载到/home目录

    经常会遇到服务用着用着发现空间不够啦 xff01 怎么办呢 xff1f 备份数据 61 使用更大的磁盘重新安装系统 61 转移数据 这样太麻烦了 xff0c 如果是生产环境 xff0c 还要停机 增加新的磁盘 这里我选择方法 2 空间不足时
  • ReactNative AsyncLocalStorageUtil is defined multiple times

    ios运行正常 xff0c 在android下运行报错如下 xff1a AS 编译报错 Type com reactnativecommunity asyncstorage AsyncLocalStorageUtil is defined
  • React Native项目gradle手动编译

    最近在折腾 xff0c 远程开发React Native 项目 xff0c 我想实现在ssh命令行中 在服务器上自动编译RN 项目 xff08 android xff09 xff0c 这样就可以使用高速的服务器来编译项目 正解 cd and
  • Docker使用笔记

    软件安装 https docs docker com engine install ubuntu 下载镜像 span class token function docker span pull ubuntu 创建一个CONTAINER 示例
  • ubuntu编译安装最新的tmux

    通过apt get安装的tmux版本比较旧 xff0c 我喜欢使用最新的版本 那就自己编译安装一下吧 很简单 xff0c 耗时1分钟左右 环境 操作系统 xff1a Ubuntu 20 04 3 LTS 安装的tmux版本 xff1a tm
  • Ubuntu Linux 更改主机名(hostname)

    操作 编辑 etc hostname文件 span class token function vim span etc hostname 文件内容修改成自己想要的名称 修改完后 xff0c 重启机器就生效了 span class token
  • docker文件目录迁移

    docker默认存放路径是 var lib docker xff0c 按理来说没有什么问题 但是在我安装操作系统时 xff0c 分区空间分的太少了 xff08 50G xff09 但是 home目录就非常大了 所以我想把docker的默认路
  • ubuntu开启ssh服务

    环境 我的测试环境是 xff1a docker中的Ubuntu 20 04 3 LTS 安装openssh server span class token function sudo span span class token functi
  • neovim无法中文显示的问题

    场景 中文的语言环境 xff0c 其他支持中文的程序 正常能显示中文 xff0c 比如 date xff0c vim都可以支持中文 就neovim显示的是英文 我下载的neovim是全功能的版本 xff0c 支持中文的 所以排除软件的原因
  • Netty使用Google Protobuf进行编解码

    文章目录 一 概述1 编解码基础2 Netty编解码器3 Protobuf概述 二 Protobuf基本使用1 引入jar包2 下载Protobuf3 编写Student proto4 生成StudentPOJO类5 服务器端6 客户端7