ProtoBuf介绍

news2024/11/18 5:55:50

1 编码和解码

编写网络应用程序时,因为数据在网络传输的都是二进制字节码数据,在发送数据时进行编码,在接受数据时进行解码

codec(编码器)的组成部分有2个:decoder(解码器)和encoder(编码器)。encoder负责将数据转换为字节码数据,而decoder负责将字节码数据转为业务数据

2 Netty的编码和解码机制问题

Netty提供的编码器:

StringEncoder:对字符串数据进行编码

ObjectEncoder:对java对象进行编码

Netty提供的解码器:

StringDecoder:对字符串数据进行编码

ObjectDecoder:对java对象进行编码

Netty本身自带的ObjectDecoder和ObjectEncoder可以实现用来对JOPO对象或各种业务对象的编码和解码。底层使用的依然是Java序列化技术,而java序列化技术本身就存在问题

  1. 无法跨语言

  1. 序列化的体积太大,是二进制编码的5倍多

  1. 序列化的性能太低

所以就出现了Google的Protobuf。

3 ProtoBuf

3.1基本介绍:

1) ProtoBuf是Google发布的开源项目,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化,它很适合做数据存储或RPC[远程过程调用]数据交换格式,目前很多公司正在由http+json->tcp+protobuf

  1. Protobuf是以message的方式来管理数据的

  1. 支持跨平台,跨语言,即客户端和服务端可以是不同语言编写的

  1. 高性能,可读性高

  1. 使用protobuf编译器能够自动生成代码,ProtoBuf是将类的定义使用.protobuf文件进行描述(注意:在idea中编写.proto文件时,会自动提示是否下载.protot编写插件,可以让语法高亮)

  1. 然后通过.protoc.exe编译器根据.protp自动生成java文件

3.2 ProtoBuf生成类(单种类型)

实例要求:客户端发送一个Student PoJo对象到服务器(通过ProtoBuf编码),服务端接收Student PoJo对象,并显示信息(通过ProtoBuf解码)

步骤一:在maven中引入ProtoBuf坐标,下载相关jar包

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

步骤二:github下载相关protoc文件(注意:下载文件需和pom文件中版本对应,否则生成java文件时会报错)

下载链接:https://github.com/protocolbuffers/protobuf/tags

然后进行相关idea配置:https://blog.csdn.net/Xin_101/article/details/123332526?

步骤三:编写代码

服务端:

public class NettyServer {

    public static void main(String[] args) throws Exception {
        //1.创建2个线程组bossGroup和workerGroup
        //2  bossGroup只是处理连接请求,workerGroup真正的和客户端进行业务处理
        //3 两个都是无限循环
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();

        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

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

            bootstrap.group(bossGroup,workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用nioSocketChannel作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG,128)//设置线程队列得到连接
                    .childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //给pipeline设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //向pipeline加入ProtobufDecoder,指定对哪种对象进行解码
                            ch.pipeline().addLast("decoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });//给workerGroup的EventLoop对应的管道设置处理器

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

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



    }

}

服务端处理器:

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    //读取数据的事件(这里读取客户端发送的消息)

    /**
     *
     * @param ctx ChannelHandlerContext ctx:上下文对象,含有管道pipeline,通道channel,地址
     * @param msg 就是客户端发送的消息
     * @throws Exception
     */
    @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 {

        ctx.writeAndFlush(Unpooled.copiedBuffer(LocalDateTime.now()+" :hello,客户端~",CharsetUtil.UTF_8));
    }

    //处理异常,一般需要关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

}

客户端:

public class NettyClient {

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

        //客户端需要一个循环组
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端的启动对象
            //注意客户端使用的是Bootstrap不是ServerBootstrap
            Bootstrap bootstrap = new Bootstrap();

            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) //设置客户端通道的实现类
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //在pipeline中加入ProtobufEncoder
                            ch.pipeline().addLast("encoder",new ProtobufEncoder());
                            ch.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();
        }


    }

}

客户端处理器:

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

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

        //发送一个Student对象到服务器
        StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(4).setName("王五").build();
        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();
    }

}

如果不想在channelRead()方法中进行强转,可以将处理器实现SimpleChannelInboundHandler类型,从而实现channelRead0()方法

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;

}

将proto文件转为java文件结果如下:

先启动客户端,再次启动服务端:

客户端控制台:

服务端控制台:

发现,已经用protobuf实现了数据的传输。

3.3 ProtoBuf生成类(多种类型)

实例要求:客户端发送一个Student PoJo \Worker PoJo对象到服务器(通过ProtoBuf编码),服务端接收Student PoJo\Worker PoJo对象,并显示信息(通过ProtoBuf解码)

服务端代码;

package com.liubujun.codec2;

import com.liubujun.codec.StudentPOJO;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;

/**
 * @Author: liubujun
 * @Date: 2023/2/15 10:24
 */


public class NettyServer {

    public static void main(String[] args) throws Exception {
        //1.创建2个线程组bossGroup和workerGroup
        //2  bossGroup只是处理连接请求,workerGroup真正的和客户端进行业务处理
        //3 两个都是无限循环
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();

        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

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

            bootstrap.group(bossGroup,workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用nioSocketChannel作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG,128)//设置线程队列得到连接
                    .childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //给pipeline设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //向pipeline加入ProtobufDecoder,指定对哪种对象进行解码
                            ch.pipeline().addLast("decoder",new ProtobufDecoder(
                                    MyDataInfo.MyMessage.getDefaultInstance()));
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });//给workerGroup的EventLoop对应的管道设置处理器

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

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



    }

}

服务端处理器:

package com.liubujun.codec2;

import com.liubujun.codec.StudentPOJO;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

import java.time.LocalDateTime;

/**
 * @Author: liubujun
 * @Date: 2023/2/15 10:25
 */


public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {


    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, 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.getAge() +" 工人姓名="+worker.getName());
        }else {
            System.out.println("传输的类型不正确");
        }
    }



    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        ctx.writeAndFlush(Unpooled.copiedBuffer(LocalDateTime.now() + " :hello,客户端~", CharsetUtil.UTF_8));
    }

    //处理异常,一般需要关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

}



客户端:

package com.liubujun.codec2;

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

/**
 * @Author: liubujun
 * @Date: 2023/2/15 10:27
 */


public class NettyClient {

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

        //客户端需要一个循环组
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端的启动对象
            //注意客户端使用的是Bootstrap不是ServerBootstrap
            Bootstrap bootstrap = new Bootstrap();

            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) //设置客户端通道的实现类
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //在pipeline中加入ProtobufEncoder
                            ch.pipeline().addLast("encoder",new ProtobufEncoder());
                            ch.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();
        }


    }

}

客户端处理器:

package com.liubujun.codec2;

import com.liubujun.codec.StudentPOJO;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

import java.util.Random;

/**
 * @Author: liubujun
 * @Date: 2023/2/15 10:28
 */


public class NettyClientHandler extends ChannelInboundHandlerAdapter {

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

        //随机的发送Student 或者 Worker对象
        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(5).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();
    }

}

proto文件:

syntax = "proto3";
option optimize_for = SPEED; //加快解析
option java_package = "com.liubujun.codec2";//指定生成到哪个包下
option java_outer_classname = "MyDataInfo"; //外部类名称

//proto可以使用message管理其它的message
message MyMessage {

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

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

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

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

先启动服务端,再启动多个客户端:发现数据正常传输

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370534.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

回归预测 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络数据回归预测

回归预测 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络数据回归预测 目录回归预测 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络数据回归预测效果一览基本介绍模型搭建程序设计参考资料效果一览 基本介绍 基于贝叶斯优化卷积双向长短期记忆网络(…

自动化测试整理 --- STAF/STAX Robot Framework

题记:上周花了点时间学习开源的自动化测试框架Robot Framework,结合自己之前的自动化经验&#xff0c;就想周末写篇文章整理下。 目前&#xff0c;所在项目的自动化测试框架是基于STAF/STAX的拓展&#xff0c;围绕STAX执行引擎&#xff0c;扩展了测试用例的创建、管理&#xf…

验证功能覆盖率收集时per_instance=1可能导致覆盖率线性增长

验证覆盖率收集时&#xff0c;发现coverage database达到了惊人的256G&#xff0c;如下&#xff1a; 进入database中的testdata目录下的用例定位发现&#xff0c;问题出在这个文件&#xff1a; testbench.inst.xml其大小基本等同于验证用例覆盖率的大小。 这个文件时怎么产…

C++函数新思想和标准的输入和输出

欢迎来观看温柔了岁月.c的博客目前设有C学习专栏C语言项目专栏数据结构与算法专栏目前主要更新C学习专栏&#xff0c;C语言项目专栏不定时更新待C专栏完毕&#xff0c;会陆续更新C项目专栏和数据结构与算法专栏一周主要三更&#xff0c;星期三&#xff0c;星期五&#xff0c;星…

【决策树】一文看懂图解决策树原理:信息熵、条件熵与信息增益

本文用过图解的方式并结合实际案例的方式讲述了决策树的基本原理&#xff0c;主要包含信息熵、条件熵与信息增益的概念与计算方式&#xff0c;以及如何选择各个决策节点&#xff08;即&#xff1a;选择信息增益最大的特征&#xff09;。 想要PDF文档的小伙伴&#xff0c;通过关…

[SCOI2005]骑士精神(C++,启发式搜索)

题目描述 输入格式 第一行有一个正整数 TTT&#xff08;T≤10T \le 10T≤10)&#xff0c;表示一共有 TTT 组数据。 接下来有 TTT 个 555 \times 555 的矩阵&#xff0c;0 表示白色骑士&#xff0c;1 表示黑色骑士&#xff0c;* 表示空位。两组数据之间没有空行。 输出格式 …

【Node.js】详细记录express路由和中间件

Node.js路由的概念什么是路由Express中的路由路由的匹配过程路由的使用创建路由模块文件注册路由模块文件为路由模块添加前缀Express中间件Express中间件格式定义中间件函数定义全局生效的中间件函数中间件的作用定义多个全局中间件局部生效的中间件定义多个局部生效的中间件了…

Ae:使用代理

如果希望加快合成的预览或渲染速度&#xff0c;可考虑对素材使用代理 Proxy。虽然在 Ae 中&#xff0c;可以指定任何的静止图像或视频为代理&#xff0c;但一般情况下还是建议创建源素材的低分辨率版本来作为代理。对素材创建或指定代理后&#xff0c;可随意切换是否使用代理来…

物联网在医疗保健领域的5大创新应用

如今&#xff0c;物联网的发展越来越迅速&#xff0c;我们无法低估物联网在当今世界的重要性。大多数人每天都会使用到物联网设备。例如&#xff0c;当你使用智能手表来跟踪你的锻炼时&#xff0c;你就间接地使用了物联网的功能。由于物联网为世界带来了很多有效的帮助&#xf…

【涨薪技术】0到1学会性能测试 —— LR录制回放事务检查点

前言 上一次推文我们分享了性能测试分类和应用领域&#xff0c;今天带大家学习性能测试工作原理、事务、检查点&#xff01;后续文章都会系统分享干货&#xff0c;带大家从0到1学会性能测试&#xff0c;另外还有教程等同步资料&#xff0c;文末免费获取~ 01、LR工作原理 通常…

【原创】java+swing+mysql生肖星座查询系统设计与实现

今天我们来开发一个比较有趣的系统&#xff0c;根据生日查询生肖星座&#xff0c;输入生日&#xff0c;系统根据这个日期自动计算出生肖和星座信息反馈到界面。我们还是使用javaswingmysql去实现这样的一个系统。 功能分析&#xff1a; 生肖星座查询系统&#xff0c;顾名思义…

【uniapp微信小程序】跨平台使用echarts的方案选择踩坑

一、前言 使用Uniapp&#xff08;vue&#xff09;开发微信小程序&#xff0c;想用echarts图表实现类似github热力图的效果。 简要列一些可行或不可行的方案。 二、方案对比 1. 【应用】&#xff1a;微信小程序原生开发 有echarts官网提供的跨平台方案&#xff1a;在微信小程…

CHATGPT是新的“搜索引擎终结者”吗?百度是否慌了

ChatGPT 以其非凡的自然语言处理 &#xff08;NLP&#xff09; 能力和清晰的响应风靡全球&#xff0c;有望带来一场重大的技术革命。在不知不觉中&#xff0c;叙事转向了ChatGPT与百度的对决&#xff0c;因为来自OpenAI的智能和健谈的聊天机器人已经慢慢获得了“潜在的百度终结…

Allegro如何快速删除孤立铜皮操作指导

Allegro如何快速删除孤立铜皮操作指导 在做PCB设计的时候,铺铜是常用的设计方式,在PCB设计完成之后,需要删除PCB上孤立的铜皮,即铜皮有网络但是却没有任何连接 如下图 通过Status报表也可以看到Isolated shapes 如何快速地删除孤立铜皮,具体操作如下 点击Shape

C++ sort()函数和priority_queue容器中比较函数的区别

普通的queue是一种先进先出的数据结构&#xff0c;元素在队列尾追加&#xff0c;而从队列头删除。priority_queue中元素被赋予优先级。在创建的时候根据优先级进行了按照从大到小或者从小到大进行了自动排列&#xff08;大顶堆or小顶堆&#xff09;。可以以O(log n) 的效率查找…

23. 合并K个升序链表

解题思路&#xff1a;两种解法&#xff0c;一种优先级队列&#xff0c;一种分治优先级队列解法&#xff1a;以节点中存储的值进行排序依次遍历所有的链表&#xff0c;把链表中的节点加入到优先级队列中依次从优先级队列的弹出并删除最小的元素加入到新的链表中&#xff0c;直到…

final关键字是什么以及final的四种用法

final定义&#xff1a; final的翻译是最终&#xff0c;也就表示着它修饰的对象是最后一次被继承等含义。 被final修饰的对象不能更改其定义以及变量值 final的四种用法 一、修饰变量 public class Demo1 {public final int a 6;public void test() {a 10;} final修饰变量…

ESP-C3入门12. HTTPS请求、堆内存使用及JSON处理

ESP-C3入门12. HTTPS请求、堆内存使用及JSON处理一、创建HTTPS请求1. 基本流程2. ESP32 使用https证书的方式(1) 内置证书(2) ESP32管理证书3. 开发环境配置(1) 引用 esp-tls 库(2) 启用 CONFIG_MBEDTLS_CERTIFICATE_BUNDLE二、堆内存的使用1. 堆内存和栈内存2. 堆内存的使用(1…

Linux基础命令-which查找命令文件位置

文章目录 which 命令功能 语法格式 基本参数 参考实例 1&#xff09;查找chmod命令的文件位置 2&#xff09;查找chmod命令的所有路径 3&#xff09;一次性查找多个命令路径 4&#xff09;组合其他命令一起使用 5&#xff09;显示命令的版本信息 命令总结 which 命…

11 udp 发送数据的流程梳理

前言 呵呵 之前曾经看到过 湖光大佬 的 tcp 的流程梳理 呵呵 很高深 有很多不明白的地方, 不光是涉及到 linux 网络处理本身的东西, 还涉及到了 tcp协议 的一些具体的实现, 是非常的复杂 这里之前 在 0voice/linux_kernel_wiki 上面看到了网络协议栈部分的梳理 呵呵 自己也…