This blog requires you to have some basic gRPC common sense. If you are completely newbie, it is recommended to visit the official website to learn more.
1. Unary RPC
Proto file is as follows
syntax = "proto3"; option go_package=".;service"; message HelloRequest { // Name of the person to greet string name = 1; } message HelloResponse { // Greeting message string greeting = 1; } service HelloService { // RPC method to say hello rpc SayHello (HelloRequest) returns (HelloResponse){} }
Use the command (note the command path and its own correspondence):
protoc -I . --go-grpc_out=require_unimplemented_servers=false:. --go_out=. *.proto
There is a corresponding directoryand
xx_grpc.
Then implement the server interface according to the corresponding directory
package main import ( "context" "fmt" "/grpc" "net" "test_grpc/service" ) type HelloService struct { } func (hs *HelloService) SayHello(ctx , req *) (*, error) { resp := &{ Greeting: ("hello %s --from Golang Server", ), } return resp, nil } func main() { // listen on 127.0.0.1:50051 listen, err := ("tcp", "127.0.0.1:50051") if err != nil { ("Error happened when listen on 127.0.0.1:50051:", err) return } // grpc server s := () // register HelloService in grpc server (s, &HelloService{}) // start rpc server ("Golang rpc server is waiting messages......") if err = (listen); err != nil { ("Error happened when start rpc server:", err) return } }
The client interface is as follows:
package main import ( "context" "fmt" "/grpc" "/grpc/credentials/insecure" "test_grpc/service" "time" ) func main() { // connect to server conn, err := ("127.0.0.1:50051", (())) if err != nil { ("Connect to rpc server err:", err) return } defer () // init service client c := (conn) // init context with timeout ctx, cancel := ((), ) defer cancel() // send message req := &{Name: "Golang"} r, err := (ctx, req) if err != nil { ("Send message err:", err) return } ("Client:", ) }
In fact, in order to better feel the cross-language call of gRPC, you can try to write client code in python, copy the proto file directly, and use the following commands in python to generate the corresponding proto file (note the command and its own correspondence):
python -m grpc_tools.protoc -I . --python_out=. --pyi_out=. --grpc_python_out=. *.proto
The client code implemented using python is as follows:
# client template # grpc server address channel = grpc.insecure_channel("127.0.0.1:50051") stub = hello_pb2_grpc.HelloServiceStub(channel) # send request response = (hello_pb2.HelloRequest(name="Python")) print() # hello Python --from Golang Server
2. Server-side streaming RPC
Re-given the proto file of this, and the server will send the data to the client in the form of streaming data.
syntax = "proto3"; option go_package=".;service"; message HelloRequest { // Name of the person to greet string name = 1; } message HelloResponse { // Greeting message string greeting = 1; } service HelloService { // RPC method to say hello rpc SayHello (HelloRequest) returns (stream HelloResponse){} }
Similarly, after generating the corresponding proto file, the corresponding file is created as the server-side code:
package main import ( "fmt" "/grpc" "net" "test_grpc/service" ) type HelloService struct { } func (hs *HelloService) SayHello(req *, stream service.HelloService_SayHelloServer) error { resp := &{ Greeting: ("hello %s --from Golang Server", ), } // Send 5 times in a row for i := 0; i < 5; i++ { if err := (resp); err != nil { return err } } return nil } func main() { // listen on 127.0.0.1:50051 listen, err := ("tcp", "127.0.0.1:50051") if err != nil { ("Error happened when listen on 127.0.0.1:50051:", err) return } // grpc server s := () // register HelloService in grpc server (s, &HelloService{}) // start rpc server ("Golang rpc server is waiting messages......") if err = (listen); err != nil { ("Error happened when start rpc server:", err) return } }
Similarly, the client's code is given:
package main import ( "context" "fmt" "/grpc" "/grpc/credentials/insecure" "io" "log" "test_grpc/service" "time" ) func main() { // connect to server conn, err := ("127.0.0.1:50051", (())) if err != nil { ("Connect to rpc server err:", err) return } defer () // init service client c := (conn) // init context with timeout ctx, cancel := ((), ) defer cancel() // send message req := &{Name: "Golang"} stream, err := (ctx, req) if err != nil { ("Send message err:", err) return } // Load the message for { resp, err := () // Read the end sign if err == { ("end.....") break } if err != nil { ("failed to receive response: %v", err) } ("Greeting: %s", ) } }
3. Client-side streaming RPC
The corresponding proto file is as follows
syntax = "proto3"; option go_package=".;service"; message HelloRequest { // Name of the person to greet string name = 1; } message HelloResponse { // Greeting message string greeting = 1; } service HelloService { // RPC method to say hello rpc SayHello (stream HelloRequest) returns (HelloResponse){} }
Similarly, use the protoc command to generate the corresponding proto file, and then write the client code first, as follows:
package main import ( "context" "fmt" "/grpc" "/grpc/credentials/insecure" "log" "test_grpc/service" "time" ) func main() { // connect to server conn, err := ("127.0.0.1:50051", (())) if err != nil { ("Connect to rpc server err:", err) return } defer () // init service client c := (conn) // init context with timeout ctx, cancel := ((), ) defer cancel() // create stream stream, err := (ctx) if err != nil { ("could not greet: %v", err) } names := []string{"World", "Gophers", "Anthropic"} for _, name := range names { // request body req := &{Name: name} if err := (req); err != nil { ("faild to send request: %v", err) } } resp, err := () if err != nil { ("%() got error %v, want %v", stream, err, nil) } ("Greeting: %s", ) }
The corresponding code to complete the server:
package main import ( "fmt" "/grpc" "io" "net" "strings" "test_grpc/service" ) type HelloService struct { } func (hs *HelloService) SayHello(stream service.HelloService_SayHelloServer) error { var strs []string for { msg, err := () if err == { break } if err != nil { return err } strs = append(strs, ) } resp := &{Greeting: (strs, " ")} err := (resp) if err != nil { return err } return nil } func main() { // listen on 127.0.0.1:50051 listen, err := ("tcp", "127.0.0.1:50051") if err != nil { ("Error happened when listen on 127.0.0.1:50051:", err) return } // grpc server s := () // register HelloService in grpc server (s, &HelloService{}) // start rpc server ("Golang rpc server is waiting messages......") if err = (listen); err != nil { ("Error happened when start rpc server:", err) return } }
4. Bidirectional streaming RPC
The new proto file is given as follows:
syntax = "proto3"; option go_package=".;service"; message HelloRequest { // Name of the person to greet string name = 1; } message HelloResponse { // Greeting message string greeting = 1; } service HelloService { // RPC method to say hello rpc SayHello (stream HelloRequest) returns (stream HelloResponse){} }
Consistent with the operation above, and the server side code is given:
package main import ( "fmt" "/grpc" "io" "log" "net" "test_grpc/service" ) type HelloService struct { } func (hs *HelloService) SayHello(stream service.HelloService_SayHelloServer) error { for { msg, err := () if err == { break } if err != nil { return err } name := resp := &{Greeting: name} if err = (resp); err != nil { ("Failed to send a resp:%s", err) } } return nil } func main() { // listen on 127.0.0.1:50051 listen, err := ("tcp", "127.0.0.1:50051") if err != nil { ("Error happened when listen on 127.0.0.1:50051:", err) return } // grpc server s := () // register HelloService in grpc server (s, &HelloService{}) // start rpc server ("Golang rpc server is waiting messages......") if err = (listen); err != nil { ("Error happened when start rpc server:", err) return } }
Also give the following client code:
package main import ( "context" "fmt" "/grpc" "/grpc/credentials/insecure" "io" "log" "test_grpc/service" "time" ) func main() { // connect to server conn, err := ("127.0.0.1:50051", (())) if err != nil { ("Connect to rpc server err:", err) return } defer () // init service client c := (conn) // init context with timeout ctx, cancel := ((), ) defer cancel() // create stream stream, err := (ctx) if err != nil { ("could not greet: %v", err) } names := []string{"World", "Gophers", "Anthropic"} waitc := make(chan struct{}) go func() { for { resp, err := () if err == { close(waitc) return } if err != nil { ("%() got error %v, want %v", stream, err, nil) } ("Greeting: %s", ) } }() go func() { for _, name := range names { // request body req := &{Name: name} if err := (req); err != nil { ("faild to send request: %v", err) } // send delay (1) } // Send the end message if err := (); err != nil { ("failed to close stream: %v", err) } }() <-waitc }
Be sure to turn off sending or avoid sending messages to a stream that has been closed. Reading messages is allowed. Here is a bit like chan.
4. ALTS
4.1 Introduction to ALTS
Application Layer Transmission Security (ALTS) is a mutual verification and transmission encryption system developed by Google. It is used to ensure the security of RPC communications within Google's infrastructure. ALTS is similar to mutual TLS, but is designed and optimized to meet the needs of Google's production environment. ALTS has the following characteristics in gRPC:
- Use ALTS as the transport protocol to create server and client of gRPC;
- ALSTS is an end-to-end protection, privacy and completion;
- Applications can access peer information such as peer service accounts;
- Support client and server awareness;
- ALTS can be used with minimal code changes;
It is worth noting that ALTS is fully functioned if the application is running in CE or GKE
4.2 gRPC client uses ALTS transmission security protocol
The gRPC client uses ALTS authentication to connect to the server, as described in the following code:
import ( "/grpc" "/grpc/credentials/alts" ) altsTC := (()) // connect to server conn, err := ("127.0.0.1:50051", (altsTC))
The gRPC server is able to use ALTS authentication to run the client to connect to it, as described below:
import ( "/grpc" "/grpc/credentials/alts" ) altsTC := (()) server := ((altsTC))
4.3 Server Authorization
gRPC has built-in server authorization support using ALTS. The gRPC client using ALTS can set up the expected server service account before establishing a connection. Then, at the end of the handshake, the server authorization ensures that the server identity matches one of the service accounts specified by the client. Otherwise, the connection will fail.
import ( "/grpc" "/grpc/credentials/alts" ) clientOpts := () = []string{expectedServerSA} altsTC := (clientOpts) conn, err := (serverAddr, (altsTC))
Summarize
This is all about this article about the four gRPC modes in Golang. For more related content in gRPC mode in Golang, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!