SoFunction
Updated on 2025-03-02

Detailed explanation of four gRPC modes in Golang

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 directoryandxx_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!