protobuf学习笔记(二):结合grpc生成客户端和服务端

上一篇文章大概讲了如何将自定义的protobuf类型的message转换成相应的go文件,这次就结合grpc写一个比较认真的客户端和服务器端例子

一、项目结构

client存放rpc服务的客户端文件

server存放rpc服务的服务端文件

protobuf存放自定义的proto文件

grpc存放生成的grpc、potobuf转换后的文件

utils存放工具性的文件

补充一个整个项目完成后展开后的结构图:

二、依赖下载

上篇文章中我们主要是安装了protoc指令程序,这次我们要安装一下

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

 这是一个转换protobuf的同时生成grpc服务的插件

此外,项目之中还要现在这两个包

github.com/golang/protobuf
google.golang.org/grpc 

三、proto文件魔改

因为这个例子还算比较严肃,所以我魔改了一下原先的文件,现在的结构是这样的:

service.proto

其定义了我们rpc服务的类型,如下

syntax="proto3";
package service;
option go_package="../grpc;grpc";
import "request.proto";
import "user.proto";
// 我们将定义相关的rpc服务,主要是在请求信息去请求我们User message
// 在此之前我对之前的protobuf结构进行了修改
// 首先将内部的role/parent/user全部整合到user.proto一个文件中去
service Test{rpc GetUser1(request.Request)returns(user.User){};//这是一个单对单的服务,传入带名字和id的信息,返回一个userrpc GetUser2(stream request.Request)returns(user.UserGroup){};// 传入多个信息,返回一个用户组rpc GetUser3(request.IdGroup)returns(stream user.User){};// 传入单个信息,返回多个用户rpc GetUser4(stream request.Request)returns(stream user.User){};//传入多个用户信息,返回多个用户实例
}

 我们看到有四个服务,分别对应着不同的input/output类型(分别在user.proto和request.proto两个文件中定义),grpc使用的是http2协议,所以有个流式传输,也就是可以一直传输某些内容,特征是在参数或者return的值前面加上“stream”关键词。以服务GetUser3为例,用户传入一个id组,会根据这个id列表中的id一直返回对应的user。

注意这里的Test,是我们这次rpc服务的命名空间,会反映到生成的grpc服务结构体名上

request.proto

定义了两个参数类型

syntax="proto3";
package request;
import "google/protobuf/any.proto";//引入any类型
import "google/protobuf/timestamp.proto";//引入时间戳类型
option go_package="../grpc;grpc";message Request{optional string name=1;//可选name参数uint64 id=2;//id参数optional google.protobuf.Any other_msg=3;//其他信息google.protobuf.Timestamp timestamp=4;//请求的时间
}// 创建一个id组成的数组
message IdGroup{repeated uint64 ids=1;google.protobuf.Timestamp timestamp=2;//请求的时间optional google.protobuf.Any other_msg=3;//其他信息
}

分别是单独的request和id组成的数组

user.proto

syntax="proto3";//顶格声明protobuf版本,默认是protobuf2
// 注意一下语句后面要加";",否则不识别
package user;//定义一下protobuf的包名
import "google/protobuf/any.proto";//引入any类型
import "google/protobuf/timestamp.proto";//引入时间戳类型
/*
import public "some path"
这个public关键词用于控制这个包的依赖是否可以传递,例子如下a.proto:
import "b.proto"
import public "c.proto"index.proto:
import "a.proto"
那么这个index文件当中除了能使用a文件中定义的变量,还能使用c文件当中的遍历,但是b文件就不能使用*/
option go_package="../grpc;grpc";//规定生成文件的输出文件,同时规定对应文件package的名称
//这里指定的 out_path 并不是绝对路径,只是相对路径或者说只是路径的一部分,和 protoc 的 --go_out 拼接后才是完整的路径。所以我的侧率就是不写go_out// 这边我们写一个User结构体
//结构体的名称我们采取的是官网上的格式,注意和golang默认格式区分
// 具体的protobuf基础类型和对应语言之间对应表,参见https://protobuf.dev/programming-guides/proto3/#specifying-field-rulesmessage User{// 保留字段和保留数字,这个用法我不是很懂,我看的资料显示如下/*如果一个字段不再需要,如果删除或者注释掉,则其他人在修改时会再次使用这些字段编号,那么旧的引用程序就可能出现一些错误,所以使用保留字段,保留已弃用的字段编号或字段名 我个人觉得这应该涉及到可拓展性的问题(难道更改的时候不会去重新生成对应的文件吗)*/reserved "hh";reserved 99 to 100;string name=1;uint64 id=2;bool merried=3;Role role=4;//这个就是role包引入的枚举类型,枚举定义在message内部就是独占枚举optional google.protobuf.Any other_msg=5;//any类型oneof child_name{string son_name=6;string daughter_name=7;//暂时看起来不同于枚举,oneof控制的事不同字段只能选一个}repeated string hobbies=8;//可重复字段,应该会生成一个切片//内嵌字段,注意tag只是在同一个message内不能重复,内嵌的字段不算// 内嵌的字段是能单独拿出来用的,比如在另一个字段中,可以使用TestUser.GameCharacter// 注意这里的行为只是定义,要使用可以如下这样写// 内嵌的role枚举enum Role{NORMAL_USER=0;VIP_USER=1;BOSS=3;};// 内嵌的parent结构message Parent{string name=1;uint32 age=2;}// 创建一个mapmap<string,Parent> parents=9;// 创建一个时间戳类型optional google.protobuf.Timestamp timestamp=10;
}
// 我尝试了一下extend关键词,试图拓展User,但是遭到了失败,不支持extension关键词,但是生成的时候又要,陷入两难// 创建一个用户组
message UserGroup{repeated User users=1;google.protobuf.Timestamp timestamp=2;optional google.protobuf.Any other_msg=3;//any类型
}

这是我们上篇文章涉及到的文件,我对user进行了删改,并加上了用户组(user组成的数组)

other_msg.proto

syntax="proto3";
package otherMsg;
option go_package="../grpc;grpc";
// 这些信息是用来填充any类型的other_msg字段的
message MsgFromWeekday{string msg=1;
}
message MsgFromWeekend{string msg=1;
}

这个则是定义了protobuf中any的结构,需要注意,protobuf中的any不是说真的啥类型都可以的,因为要对any进行反序列化,所以这里要提前定义

四、生成服务所需要的文件

我们在protobuf文件下进入命令行,输入以下指令:

protoc --go_out=. --go-grpc_out=. *.proto 

--go_out是转换成的golang文件地址 

--go-grpc_out是grpc服务文件生成地址

*.proto 则是对protobuf文件下所有proto文件进行处理

则会在我们的grpc文件中生成如下文件:

我们可以看到,明显 多了一个service_grpc.pb.go.这个就是grpc服务器和客户端的构成文件。这个文件内容呢,他非常讨厌,有点多,我看是看得懂,但是说不清,主要是包含了客户端和服务器端的一些接口、方法。

五、写一下服务端

服务端是一个main.go文件

这里我可能写的有点复杂了,主要是把函数精细化了一下

照例先贴一下

package mainimport ("context""fmt"pb "grpc_test/grpc""grpc_test/utils""io""log""net""google.golang.org/grpc"
)//第一部分:虚假数据库type User_Role int32const (User_NORMAL_USER User_Role = 0User_VIP_USER    User_Role = 1User_BOSS        User_Role = 3
)type Parent struct {Name stringAge  uint32
}
type Child struct {Gender uintName   string
}type User struct {ID      uintName    stringMerried boolRole    User_Rolehobbies []stringParents map[string]ParentChild   *Child
}var users = map[uint]User{1: {ID: 1, Name: "黄烽", Merried: false, Role: User_VIP_USER, hobbies: []string{"sleep", "game"}},2: {ID: 2, Name: "张胜前", Merried: false, Role: User_NORMAL_USER},3: {ID: 3, Name: "王怡雯", Merried: true, Role: User_VIP_USER, Child: &Child{1, "汪汪"}},4: {ID: 4, Name: "王福林", Merried: false, Role: User_NORMAL_USER, hobbies: []string{"sleep", "game"}, Parents: map[string]Parent{"father":  {"me", 99},"father2": {"you", 99},}},
}// 第二部分:定义一下对应的四个服务
type TestServer struct {pb.UnimplementedTestServer
}// 第一个服务
func (s *TestServer) GetUser1(ctx context.Context, in *pb.Request) (resp *pb.User, err error) {//GetUser1是一个单对单的服务fmt.Println("进入GetUser1服务")// 打印一下输入的基本信息err = Broadcast(in)if err != nil {log.Println(err)}// 根据id查找一下用户,这里就使用一个map代替数据库u, ok := users[uint(in.Id)]if ok {resp = GenaratePbUser(&u)// other_msg部分进行处理any, err := utils.GenerateOtherMsg("this msg is from weekday", true)if err != nil {log.Println(err)}resp.OtherMsg = any// 时间戳部分进行处理resp.Timestamp = utils.GenerateTimestamp()}fmt.Println("GetUser1服务结束")fmt.Println("---------------")return
}// 第二个服务
func (s *TestServer) GetUser2(server pb.Test_GetUser2Server) (err error) {// 该服务会不断接受requst,然后返回一个用户组var pbUsers []*pb.Userfmt.Println("进入GetUser2服务")// 接受一下流信息for {req, err := server.Recv()// 判断接受是否完成if err == io.EOF {fmt.Println("接受所有请求完成")break}if err != nil {log.Println("接受流信息失败:", err)break}//进行req的处理// 首先广播一下信息err = Broadcast(req)if err != nil {log.Println(err)}// 查找对象并返回结果切片中u, ok := users[uint(req.Id)]if ok {pbUsers = append(pbUsers, GenaratePbUser(&u))}}// 进行返回操作res := &pb.UserGroup{Users: pbUsers,}// 添加other_msg和时间戳any, err := utils.GenerateOtherMsg("this msg is from weekday", true)if err != nil {log.Println(err)}res.OtherMsg = anyres.Timestamp = utils.GenerateTimestamp()//发送信息并关闭通道server.SendAndClose(res)fmt.Println("GetUser2服务结束")fmt.Println("---------------")return
}// 第三个服务
func (s *TestServer) GetUser3(iG *pb.IdGroup, server pb.Test_GetUser3Server) (err error) {// 第三个服务,发过来一个id组,流式返回用户信息fmt.Println("进入GetUser3服务")// 打印一下传递进来的idfmt.Println("ids:", iG.Ids)// 打印一下other_msg和时间戳if iG.OtherMsg != nil {if err = utils.BroadcastMsg(iG); err != nil {log.Println(err)}}utils.BroadcastTimestamp(iG)// 查找用户并进行返回for _, v := range iG.Ids {u, ok := users[uint(v)]if ok {resp := GenaratePbUser(&u)// 创建信息any, err := utils.GenerateOtherMsg("this msg is from weekend", false)if err != nil {log.Println(err)}resp.OtherMsg = any// 时间戳部分进行处理resp.Timestamp = utils.GenerateTimestamp()err = server.Send(resp)if err != nil {log.Println("发送失败:", err)break}}}fmt.Println("GetUser3服务结束")fmt.Println("---------------")return
}// 第四个服务
func (s *TestServer) GetUser4(server pb.Test_GetUser4Server) (err error) {// 第四个服务是双向流fmt.Println("进入GetUser4服务")for {req, err := server.Recv()// 判断一下是否全部接受完成if err == io.EOF {fmt.Println("全部接受完成")break}if err != nil {log.Println("接受信息失败:", err)break}// 打印一下信息if err = Broadcast(req); err != nil {log.Println(err)}// 查找一下用户u, ok := users[uint(req.Id)]if ok {resp := GenaratePbUser(&u)any, err := utils.GenerateOtherMsg("this msg is from weekend", false)if err != nil {log.Println(err)}resp.OtherMsg = any// 时间戳部分进行处理resp.Timestamp = utils.GenerateTimestamp()err = server.Send(resp)if err != nil {log.Println("发送失败:", err)break}}}fmt.Println("GetUser4服务结束")fmt.Println("---------------")return
}// 第四个部分:开启服务器
func main() {// main函数之中开启一下服务器listener, err := net.Listen("tcp", "localhost:996")if err != nil {log.Fatalln("listen fail: ", err)}var opt []grpc.ServerOptiongrpcServer := grpc.NewServer(opt...)pb.RegisterTestServer(grpcServer, &TestServer{})fmt.Println("grpc客户端启动,正在localhost:996进行监听")err = grpcServer.Serve(listener)if err != nil {log.Fatalf("failed to serve: %v", err)}
}// 第三个部分:一些工具函数// 根据user结构体创建pb.User类型的函数
func GenaratePbUser(u *User) (pbUser *pb.User) {// 对结果进行赋值// 能直接赋值的直接赋值pbUser = &pb.User{Name:    u.Name,Id:      uint64(u.ID),Merried: u.Merried,Role:    pb.User_Role(u.Role),Hobbies: u.hobbies,Parents: make(map[string]*pb.User_Parent),}// 需要经过处理的部分// 孩子部分if u.Child != nil {// 判断一下孩子性别if u.Child.Gender == 0 {pbUser.ChildName = &pb.User_SonName{SonName: u.Child.Name}} else {pbUser.ChildName = &pb.User_DaughterName{DaughterName: u.Child.Name}}}// 家长map部分进行处理if u.Parents != nil {for k, v := range u.Parents {pbUser.Parents[k] = &pb.User_Parent{Name: v.Name, Age: v.Age}}}return
}// 打印Requst之中传入的信息
func Broadcast(in *pb.Request) (err error) {// 用户id必定存在可以不判断fmt.Println("用户id:", in.GetId())// 用户name是可选属性,所以要进行一下判断if in.Name != nil {fmt.Println("用户name:", in.GetName())}// 打印一下other_msg,这就涉及到对protobuf自定义any类型的反序列化// 因为是可选所以做一下处理if in.OtherMsg != nil {if err = utils.BroadcastMsg(in); err != nil {log.Println(err)}}// 打印一下时间的信息,这个涉及到对protobuf时间戳类型进行反序列化utils.BroadcastTimestamp(in)return
}

这个文件有点长啊,涉及到主要是四个部分

第一部分:假数据库

我创建一个比较虚假的数据库,并对一些用户进行了初始化,那样就可以按照id去查找用户

第二部分:创建服务器结构体并定义四个服务

重新定义server并重新定义四个方法。在写rpc服务我们可以看到,我们写的是一个类似golang接口的东西,具体服务的实现是没有定义的,那我们这里就要将其定义起来。核心流程就是打印request之中的信息、查找用户、返回用户。

那这部分的方法的框架应该如何定义呢?很简单,在grpc下的service_grpc.pb.go这个我们生成的rpc服务中,找到TestServer这个接口就行了,我生成的这个接口给大家看一下:

type TestServer interface {GetUser1(context.Context, *Request) (*User, error)GetUser2(Test_GetUser2Server) errorGetUser3(*IdGroup, Test_GetUser3Server) errorGetUser4(Test_GetUser4Server) errormustEmbedUnimplementedTestServer()
}

如果你看过这个文件就知道了,我们是基于pb.UnimplementedTestServer这个备用结构体的基础上重新创建了自己的TestServer,然后重新定义了四个服务

type TestServer struct {pb.UnimplementedTestServer
}

第三部分:工具函数

就是GenaratePbUserBroadcast工具函数,这部分我写的不满意,主要是写的有点复杂(各个函数的功能已经写在注释里了),此外这部分本来应该和utils包合并的,但我真心不想改,同时放一下utils包中的内容(看不懂结构的可以借助最上面的项目结构)

package utilsimport ("fmt"pb "grpc_test/grpc""google.golang.org/protobuf/types/known/anypb""google.golang.org/protobuf/types/known/timestamppb"
)// 生成any类型的other_msg信息
func GenerateOtherMsg(msg string, isWeekday bool) (any *anypb.Any, err error) {if isWeekday {any, err = anypb.New(&pb.MsgFromWeekday{Msg: msg})} else {any, err = anypb.New(&pb.MsgFromWeekday{Msg: msg})}return
}// 生成timestamp信息
func GenerateTimestamp() *timestamppb.Timestamp {return timestamppb.Now()
}// 打印other_msg的函数
// 因为需要重用,所以创建一个接口
type Messager interface {GetOtherMsg() *anypb.Any
}func BroadcastMsg(in Messager) (err error) {// protobuf中的any类型还是需要转化成protobuf之中的自定义类型(需要提前生成)//这里我选择预先不知道any所代表类型的方式进行处理// 我在这里写一个other_msg.proto去定义一下Request的other_msg的具体类型var anyMsg stringm, err := in.GetOtherMsg().UnmarshalNew()if err != nil {return err}// 因为这个other_msg可能对应两种类型,所以我们要对其进行判断switch m := m.(type) {case *pb.MsgFromWeekday:anyMsg = m.GetMsg()case *pb.MsgFromWeekend:anyMsg = m.GetMsg()default:anyMsg = "不是默认类型"// err = errors.New("传入非默认类型信息")}fmt.Println("其他信息:", anyMsg)return
}// 打印时间戳的函数
// 因为需要重用,所以还是要创建接口
type TimeCarrier interface {GetTimestamp() *timestamppb.Timestamp
}func BroadcastTimestamp(in TimeCarrier) {fmt.Println("请求/回复时间:", in.GetTimestamp().AsTime().Format("2006-01-02T15:04:05 -07:00:00"))
}

多说一下utils包的中的内容,如果你看过上篇内容就知道我们使用了protobuf的any和时间戳类型,这里GenerateOtherMsg、GenerateTimestamp分别对应了对any类型、时间戳类型的序列化,BroadcastMsg、BroadcastTimestamp则对应了反序列化(重点看一下!)

第四部分:服务端创建

grpc服务端监听,官网上抄的,后续可能会在这里加一些内容

六、客户端的创建

客户端也是一个main.go的文件,照例贴一下

package mainimport ("context""fmt"pb "grpc_test/grpc""grpc_test/utils""io""log""time""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""google.golang.org/protobuf/types/known/anypb"
)func FirstRequest(client pb.TestClient, msg *anypb.Any, id uint64, ctx context.Context) (err error) {// 该请求是发送一个请求,返回一个结果fmt.Println("开始第一个请求")req := &pb.Request{Id: id}req.OtherMsg = msgreq.Timestamp = utils.GenerateTimestamp()resp, err := client.GetUser1(ctx, req)if err != nil {log.Println("GetUser1 请求失败")return}// 简单打印一下用户名和id,otherMsg以及时间戳fmt.Println("收到反馈!")fmt.Println("用户id:", resp.Id)fmt.Println("用户名称:", resp.Name)utils.BroadcastMsg(resp)utils.BroadcastTimestamp(resp)fmt.Println("第一次请求结束!")fmt.Println("---------------")return
}func SecondRequest(client pb.TestClient, msg *anypb.Any, ids []uint64, ctx context.Context) (err error) {fmt.Println("开始第二个请求")var req = &pb.Request{}getUser2Client, err := client.GetUser2(ctx)if err != nil {log.Println("GetUser2 请求失败")return}for k, v := range ids {req.Id = vreq.OtherMsg = msgreq.Timestamp = utils.GenerateTimestamp()getUser2Client.Send(req)if k == len(ids)-1 {getUser2Client.CloseSend()break}}userGroup, err := getUser2Client.CloseAndRecv()if err != nil {log.Println("GetUser2 接受返回值失败")return}// 打印一下所有的userGroup的id和name、时间戳for _, u := range userGroup.GetUsers() {fmt.Println("收到反馈!")fmt.Printf("%v:%v\n", u.Id, u.Name)}utils.BroadcastTimestamp(userGroup)fmt.Println("第二次请求结束!")fmt.Println("---------------")return
}func ThirdRequest(client pb.TestClient, msg *anypb.Any, ids []uint64, ctx context.Context) (err error) {fmt.Println("开始第三个请求")iG := &pb.IdGroup{Ids:       ids,Timestamp: utils.GenerateTimestamp(),OtherMsg:  msg,}Test_GetUser3Client, err := client.GetUser3(ctx, iG)if err != nil {log.Println("GetUser3 请求失败")return}for {user, err := Test_GetUser3Client.Recv()// 首先判断一下是否完全接收if err == io.EOF {fmt.Println("所有回复接收完成")break}if err != nil {log.Println("GetUser3 接受返回值失败")return err}// 打印一下user的信息fmt.Println("收到反馈!")fmt.Println("用户id:", user.Id)fmt.Println("用户名称:", user.Name)utils.BroadcastTimestamp(user)}fmt.Println("第三次请求结束!")fmt.Println("---------------")return
}
func ForthRequest(client pb.TestClient, msg *anypb.Any, ids []uint64, ctx context.Context) (err error) {fmt.Println("开始第四个请求")var req = &pb.Request{}test_GetUser4Client, err := client.GetUser4(ctx)if err != nil {log.Println("GetUser4 请求失败")return}for i, v := range ids {// 复用上面的reqreq.Id = vreq.Timestamp = utils.GenerateTimestamp()req.OtherMsg = msgerr = test_GetUser4Client.Send(req)if err != nil {log.Println("GetUser4 发送请求失败")return}u, err := test_GetUser4Client.Recv()if err != nil {log.Println("GetUser4 接受信息失败")return err}// 打印一下返回值的信息fmt.Println("收到反馈!")fmt.Println("用户id:", u.Id)fmt.Println("用户名称:", u.Name)utils.BroadcastTimestamp(u)// 加一个关闭send的判断if i == len(ids)-1 {err = test_GetUser4Client.CloseSend()if err != nil {log.Println("关闭send失败:", err)}break}}fmt.Println("第四次请求结束!")fmt.Println("---------------")return
}func main() {// 连接服务器conn, err := grpc.Dial("localhost:996", grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Fatal("dial fail: ", err)}defer conn.Close()testClient := pb.NewTestClient(conn)// 创建共用的ctxctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()// 因为就两个any类型的message,所以这边事先创建一下weekdayMsg, err := utils.GenerateOtherMsg("this msg is from weekday", true)if err != nil {log.Println("创建message失败", err)}weekendMsg, err := utils.GenerateOtherMsg("this msg is from weekend", false)if err != nil {log.Println("创建message失败", err)}// 开始进行任务// 请求第一个服务// 该请求是一来一回err = FirstRequest(testClient, weekdayMsg, 1, ctx)if err != nil {log.Println("FirstRequest", err)return}// 进行第二个请求// 该请求是发送多个请求,返回一个用户组err = SecondRequest(testClient, weekendMsg, []uint64{1, 2}, ctx)if err != nil {log.Println("SecondRequest", err)return}// 进行第三个请求// 发过来一个id组,流式返回用户信息err = ThirdRequest(testClient, weekdayMsg, []uint64{3, 4}, ctx)if err != nil {log.Println("ThirdRequest", err)return}// 进行第四个请求// 双方都是流式err = ForthRequest(testClient, weekendMsg, []uint64{2, 3}, ctx)if err != nil {log.Println("ForthRequest", err)return}}

客户端比较简单,但我为了清楚,还是对四个服务的请求各自写了一个函数,主要还是请求接受回复那一套,还是比较清楚的。

需要注意的是,在第四次请求中,也就是对应GetUser4服务的这个请求(如果你记性好的话,应该记得他是一个“流对流”的服务),context值一定不能是初始值(即ctx:=context.Background()),一定要有时间的限制,否则会报错

七、服务端和客户端的运行测试

期待已久的时刻开启了,现在在项目中的server和client文件下打开命令行(server和client下都是两个main.go文件),先server,后client分别输入

go run .

完美传输

本文结束,有机会上个gitee地址。 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IP可以申请SSL证书吗?

目录 背景&#xff1a; 申请IP证书的基本条件&#xff1a; 支持IP地址的证书类型&#xff1a; 为什么要申请IP地址证书&#xff1f; 如何申请IP地址证书 背景&#xff1a; IP地址是可以实现https加密需求的&#xff0c;且IP SSL证书可以完美的解决企业对于IP地址实现http…

第十四届蓝桥杯省赛C++ C组所有题目以及题解(C++)【编程题均通过100%测试数据】

第一题《求和》【简单模拟】 【问题描述】 求1&#xff08;含&#xff09;至20230408&#xff08;含&#xff09;中每个数的和。 【答案提交】 这是一道结果填空的题&#xff0c;你只需要算出结果后提交即可。本题的结果为一个整数&#xff0c;在提交答案时只填写这个整数&…

C++ —— C++11新增语法

目录 一&#xff0c;列表初始化 1.1 这是什么&#xff1f; 1.2 initializer_list 1.3 在容器的运用 1.4 STL中的变化 二&#xff0c;右值引用和左值引用 2.1 是什么&#xff1f; 2.2 这两个东西有啥关系&#xff1f; 2.3 有啥用&#xff1f; 三&#xff0c;*移动构…

【c 语言 】malloc函数详解

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;C语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&…

Pytorch复现 Transformer cssdn

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;TensorFlow入门实战&#xff5c;第3周&#xff1a;天气识别&#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 一、多头注意力机制 import torch import…

智慧油站系统的物联网技术方案

智慧油站系统的物联网技术方案 智慧油站远程监控云平台是物联网技术在石油能源行业的深度应用&#xff0c;通过集成先进的传感器技术、大数据分析、云计算及人工智能等前沿科技手段&#xff0c;实现对加油站运营全流程的智能化管理与服务升级。其物联网技术方案主要包括以下几…

Linux(4)常见操作整理-静态路由-双网卡-文件上传下载-运维思路-性能监测方法-jar包查找

五、常见操作 1、静态路由配置 【描述】&#xff1a;当前ifconfig eno16777728 对应ip&#xff1a;172.41.0.120 【解决】&#xff1a; &#xff08;1&#xff09; [rootlocalhost ~]# cd /etc/sysconfig/network-scripts/ &#xff08;2&#xff09; 添加文件&#xff1a…

k8s笔记28--快速在ubuntu上基于二进制和源码安装containerd

k8s笔记28--快速在ubuntu上基于二进制和源码安装containerd 介绍containerd 安装方法二进制文件安装源码构建安装 注意事项说明 介绍 Containerd是一个工业标准的容器运行时&#xff0c;它强调简单、健壮和可移植性。它可作为Linux和Windows的守护进程&#xff0c;能管理主机系…

进程控制 | 进程终止 | 进程等待 | 进程替换

文章目录 1.进程终止1.1.理解C/C中main函数的返回值1.2.进程终止的方式 2.进程等待2.1.进程等待必要性2.2.进程等待系统调用2.3.获取子进程status 3.进程替换3.1.进程程序替换引出3.2.如何进行进程的替换3.3.C/C调用python 博客的完整代码连接&#xff1a; gitee 1.进程终止 …

【Python系列】Python 解释器的站点配置

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

统信 UOS V20 一键安装 Oracle 19C(19.22)单机版

Oracle 一键安装脚本&#xff0c;演示 统信 UOS V20 一键安装 Oracle 19C&#xff08;19.22&#xff09;单机版过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 ORALCE PSU/OJVM 等补丁自动安装&#xff09; ⭐️ 脚本下载地址&#xff1a;She…

Django屏蔽Server响应头信息

一、背景 最近我们被安全部门的漏洞扫描工具扫出了一个服务端口的漏洞。这个服务本身是一个Django启动的web服务&#xff0c;并且除了登录页面&#xff0c;其它页面或者接口都需要进行登录授权才能进行访问。 漏洞扫描信息和提示修复信息如下: 自然这些漏洞如何修复&#xff0c…