Go gRPC Basics
Geoff Flarity
Inspired by this TutorialEdge video. I wanted a text version that with essentially the same content. There's a lot sample git repos out there, and instructions for updating the boiler plate. This a Go gRPC Hello World tutorial from scratch.
Install protoc and the rest of the tool chain for using protoc with Go and gRPC.
brew install protoc
brew install protoc-gen-go
brew install protoc-gen-go-grpc
Initialize your new go module:
go mod init hello_grpc
go get google.golang.org/grpc
Create a protocol buffer for this service. Note the option go_package = "hello_grpc/protos";
which is relatively new vs the the video tutorial above (reference).
syntax = "proto3";
package chat;
option go_package = "/chat";
message Message {
string body = 1;
}
service ChatService {
rpc SayHello(Message) returns (Message) {}
}
Compile/generate the protocol buffer go code:
mkdir chat
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=./chat --go-grpc_opt=paths=source_relative \
chat.proto
# we can see what's been generated using tree
tree chat
chat
├── chat.pb.go
└── chat_grpc.pb.go
1 directory, 2 files
Now let's create the service
go get golang.org/x/net/context
Under the chat directory create chat.go:
package chat
import (
"context"
"log"
)
type Service struct {
// This is a requirement of the interface required for registration below.
UnimplementedChatServiceServer
}
// this matches our proto rpc
func (s *Service) SayHello(ctx context.Context, message *Message) (*Message, error) {
log.Printf("Message Resceived From Client: %s", message.Body)
return &Message{Body: "Hello from the server!"}, nil
}
Now let's create the server (I chose to put this under server directory).
package main
import (
"google.golang.org/grpc"
"hello_grpc/chat"
"log"
"net"
)
func main() {
lis, err := net.Listen("tcp", ":9000")
if err != nil {
// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
log.Fatalf("failed to listen: %s", err.Error())
}
s := grpc.NewServer()
// the PB code we generated before providers a helper for registeration
chat.RegisterChatServiceServer(s, &chat.Service{})
// Under the hood it's just doing the following which seems like a better delegation of responsibility IMHO.
// However, you are supposed to use the above canonically.
// s.RegisterService(&chat.ChatService_ServiceDesc, s)
// Start listening now that the service is registered. This will block indefinitely. If you want to do something
// else you'll need to wrap this in a goroutine.
if err = s.Serve(lis); err != nil {
log.Fatalf("gRPC server failed to listen: %s", err.Error())
}
}
Now let's create the client (I chose to put this under the client directory):
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"hello_grpc/chat"
"log"
)
func main() {
// make the connection
conn, err := grpc.Dial(":9000", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Could not connect to gRPC server: %s", err.Error())
}
// service client
c := chat.NewChatServiceClient(conn)
r, err := c.SayHello(context.Background(), &chat.Message{Body: "Hello from the client!"})
if err != nil {
log.Fatalf("Could not say hello: %s", err.Error())
}
log.Println(r.Body)
}
Putting it all together, you can now run the server:
go run server.go
Then run the client in another terminal:
go run client.go
You should see something similar to
% go run server.go
<silience>
# in another terminal
% go run client.go
2023/08/09 13:12:12 Hello from the server!
# back in the first terminal
2023/08/09 13:12:12 Message Resceived From Client: Hello from the client!
You find the code from above here.