主頁 > 後端開發 > gRPC,爆贊

gRPC,爆贊

2021-10-14 06:17:28 後端開發

原文鏈接: gRPC,爆贊

gRPC 這項技術真是太棒了,介面約束嚴格,性能還高,在 k8s 和很多微服務框架中都有應用,

作為一名程式員,學就對了,

之前用 Python 寫過一些 gRPC 服務,現在準備用 Go 來感受一下原汁原味的 gRPC 程式開發,

本文的特點是直接用代碼說話,通過開箱即用的完整代碼,來介紹 gRPC 的各種使用方法,

代碼已經上傳到 GitHub,下面正式開始,

介紹

gRPC 是 Google 公司基于 Protobuf 開發的跨語言的開源 RPC 框架,gRPC 基于 HTTP/2 協議設計,可以基于一個 HTTP/2 鏈接提供多個服務,對于移動設備更加友好,

入門

首先來看一個最簡單的 gRPC 服務,第一步是定義 proto 檔案,因為 gRPC 也是 C/S 架構,這一步相當于明確介面規范,

proto

syntax = "proto3";

package proto;

// The greeting service definition.
service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

使用 protoc-gen-go 內置的 gRPC 插件生成 gRPC 代碼:

protoc --go_out=plugins=grpc:. helloworld.proto

執行完這個命令之后,會在當前目錄生成一個 helloworld.pb.go 檔案,檔案中分別定義了服務端和客戶端的介面:

// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreeterClient interface {
	// Sends a greeting
	SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}

// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
	// Sends a greeting
	SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

接下來就是寫服務端和客戶端的代碼,分別實作對應的介面,

server

package main

import (
	"context"
	"fmt"
	"grpc-server/proto"
	"log"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

type greeter struct {
}

func (*greeter) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
	fmt.Println(req)
	reply := &proto.HelloReply{Message: "hello"}
	return reply, nil
}

func main() {
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	server := grpc.NewServer()
	// 注冊 grpcurl 所需的 reflection 服務
	reflection.Register(server)
	// 注冊業務服務
	proto.RegisterGreeterServer(server, &greeter{})

	fmt.Println("grpc server start ...")
	if err := server.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

client

package main

import (
	"context"
	"fmt"
	"grpc-client/proto"
	"log"

	"google.golang.org/grpc"
)

func main() {
	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	client := proto.NewGreeterClient(conn)
	reply, err := client.SayHello(context.Background(), &proto.HelloRequest{Name: "zhangsan"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(reply.Message)
}

這樣就完成了最基礎的 gRPC 服務的開發,接下來我們就在這個「基礎模板」上不斷豐富,學習更多特性,

流方式

接下來看看流的方式,顧名思義,資料可以源源不斷的發送和接收,

流的話分單向流和雙向流,這里我們直接通過雙向流來舉例,

proto

service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
    // Sends stream message
    rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply) {}
}

增加一個流函式 SayHelloStream,通過 stream 關鍵詞來指定流特性,

需要重新生成 helloworld.pb.go 檔案,這里不再多說,

server

func (*greeter) SayHelloStream(stream proto.Greeter_SayHelloStreamServer) error {
	for {
		args, err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		fmt.Println("Recv: " + args.Name)
		reply := &proto.HelloReply{Message: "hi " + args.Name}

		err = stream.Send(reply)
		if err != nil {
			return err
		}
	}
}

在「基礎模板」上增加 SayHelloStream 函式,其他都不需要變,

client

client := proto.NewGreeterClient(conn)

// 流處理
stream, err := client.SayHelloStream(context.Background())
if err != nil {
	log.Fatal(err)
}

// 發送訊息
go func() {
	for {
		if err := stream.Send(&proto.HelloRequest{Name: "zhangsan"}); err != nil {
			log.Fatal(err)
		}
		time.Sleep(time.Second)
	}
}()

// 接收訊息
for {
	reply, err := stream.Recv()
	if err != nil {
		if err == io.EOF {
			break
		}
		log.Fatal(err)
	}
	fmt.Println(reply.Message)
}

通過一個 goroutine 發送訊息,主程式的 for 回圈接收訊息,

執行程式會發現,服務端和客戶端都不斷有列印輸出,

驗證器

接下來是驗證器,這個需求是很自然會想到的,因為涉及到介面之間的請求,那么對引數進行適當的校驗是很有必要的,

在這里我們使用 protoc-gen-govalidators 和 go-grpc-middleware 來實作,

先安裝:

go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators

go get github.com/grpc-ecosystem/go-grpc-middleware

接下來修改 proto 檔案:

proto

import "github.com/mwitkow/[email protected]/validator.proto";

message HelloRequest {
    string name = 1 [
        (validator.field) = {regex: "^[z]{2,5}$"}
    ];
}

在這里對 name 引數進行校驗,需要符合正則的要求才可以正常請求,

還有其他驗證規則,比如對數字大小進行驗證等,這里不做過多介紹,

接下來生成 *.pb.go 檔案:

protoc  \
    --proto_path=${GOPATH}/pkg/mod \
    --proto_path=${GOPATH}/pkg/mod/github.com/gogo/[email protected] \
    --proto_path=. \
    --govalidators_out=. --go_out=plugins=grpc:.\
    *.proto

執行成功之后,目錄下會多一個 helloworld.validator.pb.go 檔案,

這里需要特別注意一下,使用之前的簡單命令是不行的,需要使用多個 proto_path 引數指定匯入 proto 檔案的目錄,

官方給了兩種依賴情況,一個是 google protobuf,一個是 gogo protobuf,我這里使用的是第二種,

即使使用上面的命令,也有可能會遇到這個報錯:

Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors

但不要慌,大概率是參考路徑的問題,一定要看好自己的安裝版本,以及在 GOPATH 中的具體路徑,

最后是服務端代碼改造:

引入包:

grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator"

然后在初始化的時候增加驗證器功能:

server := grpc.NewServer(
	grpc.UnaryInterceptor(
		grpc_middleware.ChainUnaryServer(
			grpc_validator.UnaryServerInterceptor(),
		),
	),
	grpc.StreamInterceptor(
		grpc_middleware.ChainStreamServer(
			grpc_validator.StreamServerInterceptor(),
		),
	),
)

啟動程式之后,我們再用之前的客戶端代碼來請求,會收到報錯:

2021/10/11 18:32:59 rpc error: code = InvalidArgument desc = invalid field Name: value 'zhangsan' must be a string conforming to regex "^[z]{2,5}$"
exit status 1

因為 name: zhangsan 是不符合服務端正則要求的,但是如果傳參 name: zzz,就可以正常回傳了,

Token 認證

終于到認證環節了,先看 Token 認證方式,然后再介紹證書認證,

先改造服務端,有了上文驗證器的經驗,那么可以采用同樣的方式,寫一個攔截器,然后在初始化 server 時候注入,

認證函式:

func Auth(ctx context.Context) error {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return fmt.Errorf("missing credentials")
	}

	var user string
	var password string

	if val, ok := md["user"]; ok {
		user = val[0]
	}
	if val, ok := md["password"]; ok {
		password = val[0]
	}

	if user != "admin" || password != "admin" {
		return grpc.Errorf(codes.Unauthenticated, "invalid token")
	}

	return nil
}

metadata.FromIncomingContext 從背景關系讀取用戶名和密碼,然后和實際資料進行比較,判斷是否通過認證,

攔截器:

var authInterceptor grpc.UnaryServerInterceptor
authInterceptor = func(
	ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (resp interface{}, err error) {
	//攔截普通方法請求,驗證 Token
	err = Auth(ctx)
	if err != nil {
		return
	}
	// 繼續處理請求
	return handler(ctx, req)
}

初始化:

server := grpc.NewServer(
	grpc.UnaryInterceptor(
		grpc_middleware.ChainUnaryServer(
			authInterceptor,
			grpc_validator.UnaryServerInterceptor(),
		),
	),
	grpc.StreamInterceptor(
		grpc_middleware.ChainStreamServer(
			grpc_validator.StreamServerInterceptor(),
		),
	),
)

除了上文的驗證器,又多了 Token 認證攔截器 authInterceptor

最后是客戶端改造,客戶端需要實作 PerRPCCredentials 介面,

type PerRPCCredentials interface {
    // GetRequestMetadata gets the current request metadata, refreshing
    // tokens if required. This should be called by the transport layer on
    // each request, and the data should be populated in headers or other
    // context. If a status code is returned, it will be used as the status
    // for the RPC. uri is the URI of the entry point for the request.
    // When supported by the underlying implementation, ctx can be used for
    // timeout and cancellation.
    // TODO(zhaoq): Define the set of the qualified keys instead of leaving
    // it as an arbitrary string.
    GetRequestMetadata(ctx context.Context, uri ...string) (
        map[string]string,    error,
    )
    // RequireTransportSecurity indicates whether the credentials requires
    // transport security.
    RequireTransportSecurity() bool
}

GetRequestMetadata 方法回傳認證需要的必要資訊,RequireTransportSecurity 方法表示是否啟用安全鏈接,在生產環境中,一般都是啟用的,但為了測驗方便,暫時這里不啟用了,

實作介面:

type Authentication struct {
	User     string
	Password string
}

func (a *Authentication) GetRequestMetadata(context.Context, ...string) (
	map[string]string, error,
) {
	return map[string]string{"user": a.User, "password": a.Password}, nil
}

func (a *Authentication) RequireTransportSecurity() bool {
	return false
}

連接:

conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithPerRPCCredentials(&auth))

好了,現在我們的服務就有 Token 認證功能了,如果用戶名或密碼錯誤,客戶端就會收到:

2021/10/11 20:39:35 rpc error: code = Unauthenticated desc = invalid token
exit status 1

如果用戶名和密碼正確,則可以正常回傳,

單向證書認證

證書認證分兩種方式:

  1. 單向認證
  2. 雙向認證

先看一下單向認證方式:

生成證書

首先通過 openssl 工具生成自簽名的 SSL 證書,

1、生成私鑰:

openssl genrsa -des3 -out server.pass.key 2048

2、去除私鑰中密碼:

openssl rsa -in server.pass.key -out server.key

3、生成 csr 檔案:

openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=beijing/L=beijing/O=grpcdev/OU=grpcdev/CN=example.grpcdev.cn"

4、生成證書:

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

再多說一句,分別介紹一下 X.509 證書包含的三個檔案:key,csr 和 crt,

  • key: 服務器上的私鑰檔案,用于對發送給客戶端資料的加密,以及對從客戶端接收到資料的解密,
  • csr: 證書簽名請求檔案,用于提交給證書頒發機構(CA)對證書簽名,
  • crt: 由證書頒發機構(CA)簽名后的證書,或者是開發者自簽名的證書,包含證書持有人的資訊,持有人的公鑰,以及簽署者的簽名等資訊,

gRPC 代碼

證書有了之后,剩下的就是改造程式了,首先是服務端代碼,

// 證書認證-單向認證
creds, err := credentials.NewServerTLSFromFile("keys/server.crt", "keys/server.key")
if err != nil {
	log.Fatal(err)
	return
}

server := grpc.NewServer(grpc.Creds(creds))

只有幾行代碼需要修改,很簡單,接下來是客戶端,

由于是單向認證,不需要為客戶端單獨生成證書,只需要把服務端的 crt 檔案拷貝到客戶端對應目錄下即可,

// 證書認證-單向認證
creds, err := credentials.NewClientTLSFromFile("keys/server.crt", "example.grpcdev.cn")
if err != nil {
	log.Fatal(err)
	return
}
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(creds))

好了,現在我們的服務就支持單向證書認證了,

但是還沒完,這里可能會遇到一個問題:

2021/10/11 21:32:37 rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0"
exit status 1

原因是 Go 1.15 開始廢棄了 CommonName,推薦使用 SAN 證書,如果想要兼容之前的方式,可以通過設定環境變數的方式支持,如下:

export GODEBUG="x509ignoreCN=0"

但是需要注意,從 Go 1.17 開始,環境變數就不再生效了,必須通過 SAN 方式才行,所以,為了后續的 Go 版本升級,還是早日支持為好,

雙向證書認證

最后來看看雙向證書認證,

生成帶 SAN 的證書

還是先生成證書,但這次有一點不一樣,我們需要生成帶 SAN 擴展的證書,

什么是 SAN?

SAN(Subject Alternative Name)是 SSL 標準 x509 中定義的一個擴展,使用了 SAN 欄位的 SSL 證書,可以擴展此證書支持的域名,使得一個證書可以支持多個不同域名的決議,

將默認的 OpenSSL 組態檔拷貝到當前目錄,

Linux 系統在:

/etc/pki/tls/openssl.cnf

Mac 系統在:

/System/Library/OpenSSL/openssl.cnf

修改臨時組態檔,找到 [ req ] 段落,然后將下面陳述句的注釋去掉,

req_extensions = v3_req # The extensions to add to a certificate request

接著添加以下配置:

[ v3_req ]
# Extensions to add to a certificate request

basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = www.example.grpcdev.cn

[ alt_names ] 位置可以配置多個域名,比如:

[ alt_names ]
DNS.1 = www.example.grpcdev.cn
DNS.2 = www.test.grpcdev.cn

為了測驗方便,這里只配置一個域名,

1、生成 ca 證書:

openssl genrsa -out ca.key 2048

openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.grpcdev.com" -days 5000 -out ca.pem

2、生成服務端證書:

# 生成證書
openssl req -new -nodes \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=grpcdev/OU=grpcdev/CN=www.example.grpcdev.cn" \
    -config <(cat openssl.cnf \
        <(printf "[SAN]\nsubjectAltName=DNS:www.example.grpcdev.cn")) \
    -keyout server.key \
    -out server.csr
    
# 簽名證書
openssl x509 -req -days 365000 \
    -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial \
    -extfile <(printf "subjectAltName=DNS:www.example.grpcdev.cn") \
    -out server.pem

3、生成客戶端證書:

# 生成證書
openssl req -new -nodes \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=grpcdev/OU=grpcdev/CN=www.example.grpcdev.cn" \
    -config <(cat openssl.cnf \
        <(printf "[SAN]\nsubjectAltName=DNS:www.example.grpcdev.cn")) \
    -keyout client.key \
    -out client.csr

# 簽名證書
openssl x509 -req -days 365000 \
    -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial \
    -extfile <(printf "subjectAltName=DNS:www.example.grpcdev.cn") \
    -out client.pem

gRPC 代碼

接下來開始修改代碼,先看服務端:

// 證書認證-雙向認證
// 從證書相關檔案中讀取和決議資訊,得到證書公鑰、密鑰對
cert, _ := tls.LoadX509KeyPair("cert/server.pem", "cert/server.key")
// 創建一個新的、空的 CertPool
certPool := x509.NewCertPool()
ca, _ := ioutil.ReadFile("cert/ca.pem")
// 嘗試決議所傳入的 PEM 編碼的證書,如果決議成功會將其加到 CertPool 中,便于后面的使用
certPool.AppendCertsFromPEM(ca)
// 構建基于 TLS 的 TransportCredentials 選項
creds := credentials.NewTLS(&tls.Config{
	// 設定證書鏈,允許包含一個或多個
	Certificates: []tls.Certificate{cert},
	// 要求必須校驗客戶端的證書,可以根據實際情況選用以下引數
	ClientAuth: tls.RequireAndVerifyClientCert,
	// 設定根證書的集合,校驗方式使用 ClientAuth 中設定的模式
	ClientCAs: certPool,
})

再看客戶端:

// 證書認證-雙向認證
// 從證書相關檔案中讀取和決議資訊,得到證書公鑰、密鑰對
cert, _ := tls.LoadX509KeyPair("cert/client.pem", "cert/client.key")
// 創建一個新的、空的 CertPool
certPool := x509.NewCertPool()
ca, _ := ioutil.ReadFile("cert/ca.pem")
// 嘗試決議所傳入的 PEM 編碼的證書,如果決議成功會將其加到 CertPool 中,便于后面的使用
certPool.AppendCertsFromPEM(ca)
// 構建基于 TLS 的 TransportCredentials 選項
creds := credentials.NewTLS(&tls.Config{
	// 設定證書鏈,允許包含一個或多個
	Certificates: []tls.Certificate{cert},
	// 要求必須校驗客戶端的證書,可以根據實際情況選用以下引數
	ServerName: "www.example.grpcdev.cn",
	RootCAs:    certPool,
})

大功告成,

Python 客戶端

前面已經說了,gRPC 是跨語言的,那么,本文最后我們用 Python 寫一個客戶端,來請求 Go 服務端,

使用最簡單的方式來實作:

proto 檔案就使用最開始的「基礎模板」的 proto 檔案:

syntax = "proto3";

package proto;

// The greeting service definition.
service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
    // Sends stream message
    rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply) {}
}

// The request message containing the user's name.
 message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

同樣的,也需要通過命令列的方式生成 pb.py 檔案:

python3 -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. ./*.proto

執行成功之后會在目錄下生成 helloworld_pb2.py 和 helloworld_pb2_grpc.py 兩個檔案,

這個程序也可能會報錯:

ModuleNotFoundError: No module named 'grpc_tools'

別慌,是缺少包,安裝就好:

pip3 install grpcio
pip3 install grpcio-tools

最后看一下 Python 客戶端代碼:

import grpc

import helloworld_pb2
import helloworld_pb2_grpc


def main():
    channel = grpc.insecure_channel("127.0.0.1:50051")
    stub = helloworld_pb2_grpc.GreeterStub(channel)
    response = stub.SayHello(helloworld_pb2.HelloRequest(name="zhangsan"))
    print(response.message)


if __name__ == '__main__':
    main()

這樣,就可以通過 Python 客戶端請求 Go 啟的服務端服務了,

總結

本文通過實戰角度出發,直接用代碼說話,來說明 gRPC 的一些應用,

內容包括簡單的 gRPC 服務,流處理模式,驗證器,Token 認證和證書認證,

除此之外,還有其他值得研究的內容,比如超時控制,REST 介面和負載均衡等,以后還會抽時間繼續完善剩下這部分內容,

本文中的代碼都經過測驗驗證,可以直接執行,并且已經上傳到 GitHub,小伙伴們可以一遍看原始碼,一遍對照文章內容來學習,


原始碼地址:

  • https://github.com/yongxinz/go-example/tree/main/grpc-example
  • https://github.com/yongxinz/gopher/tree/main/blog

往期文章:

  • 推薦三個實用的 Go 開發工具
  • 被 Docker 日志坑慘了
  • 使用 grpcurl 通過命令列訪問 gRPC 服務
  • 這個 TCP 問題你得懂:Cannot assign requested address

參考文章:

  • https://github.com/mwitkow/go-proto-validators
  • https://github.com/Bingjian-Zhu/go-grpc-example
  • http://gaodongfei.com/archives/start-grpc
  • https://liaoph.com/openssl-san/
  • https://www.cnblogs.com/jackluo/p/13841286.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/311917.html

標籤:Go

上一篇:安卓系統中的埃塞俄比亞日歷

下一篇:使用Jsonp實作跨域請求

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more