Implementing a Chat Service with Bidirectional Streaming gRPC in Golang
Written on
Chapter 1: Introduction
This article will guide you through the process of building a chat service utilizing bidirectional streaming gRPC with Golang. We'll also explore how to verify the implementation using grpcui and unit tests.
Bidirectional Streaming gRPC Overview
Bidirectional streaming gRPC allows both the client and server to send a series of messages through a read-write stream. Each stream operates independently, enabling clients and servers to read and write messages in any order. In a bidirectional streaming gRPC call, both parties can send multiple messages to each other and can stop sending or receiving messages at any time, potentially triggering various business logic or errors.
The format for this communication is "RPC streaming requests yield RPC streaming responses." Below is a visual representation of how bidirectional streaming RPC functions.
Chapter 2: Building the Bidirectional Streaming gRPC
In a previous article, I outlined the creation of a client streaming gRPC. For this project, the goal is to implement two bidirectional streaming gRPC services for chat functionality: one using byte data and the other using string data. Both implementations will be accompanied by unit tests and verified using grpcui.
Let's dive into the process.
Step 1: Folder Structure
Create a directory named clientstream. The name bidirectstream denotes the type of gRPC, but you can choose any name you prefer. While Golang typically suggests using concise names without capital letters or underscores, I opted for a name that aligns with the gRPC type.
Step 2: Set Up Directories
Create the following folders within your project:
- proto
- client
- server
Once completed, your folder structure should resemble the image below.
Step 3: Create and Populate the Proto File
Create a proto file named bidirectstream.proto in the proto folder. This name also reflects the gRPC type.
After creating the bidirectstream.proto, fill it with the following content:
syntax = "proto3";
option go_package = "alltypes/bidirectstream/proto";
package pb;
service Phone {
rpc SendMsgBytes(stream SendMsgBytesRequest) returns (stream SendMsgBytesResponse) {}
rpc SendMsgStr(stream SendMsgStrRequest) returns (stream SendMsgStrResponse) {}
}
message SendMsgBytesRequest {
bytes msg = 1;
}
message SendMsgBytesResponse {
bytes msg = 1;
}
message SendMsgStrRequest {
string msg = 1;
}
message SendMsgStrResponse {
string msg = 1;
}
Step 4: Generate pb.go Files
Open your terminal and execute the following command to automatically generate pb.go files:
% protoc --go_out=. --go-grpc_opt=require_unimplemented_servers=false --go_opt=paths=source_relative
--go-grpc_out=. --go-grpc_opt=paths=source_relative
proto/bidirectstream.proto
After running the command, you will see newly generated files: bidirectstream.pb.go and bidirectstream_grpc.pb.go. Do not modify these files directly; instead, make changes to the bidirectstream.proto file and re-run the command to regenerate the files.
Step 5: Create the Server
Navigate to the server folder and create a file named server.go. The next steps include creating a phoneServer struct and implementing the required methods.
- Define the Phone Server Struct: The phoneServer struct is created from the automatically generated bds.PhoneServer interface.
- Set Up a TCP Listener: In server.go, establish a listener on TCP port 50058.
- Log Server Start: Implement a log statement indicating that the server has started.
- Register the Server: Create a new gRPC server instance and register the phoneServer with it.
- Implement gRPC Methods: Implement the methods SendMsgBytes and SendMsgStr for processing incoming messages.
The complete code for server.go is as follows:
package main
import (
"errors"
"io"
"log"
"net"
"strings"
"time"
bds "github.com/ramseyjiang/go_mid_to_senior/pkgusages/grpc/alltypes/bidirectstream/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
type phoneServer struct {
bds.PhoneServer
calls []string
}
func main() {
listener, err := net.Listen("tcp", "0.0.0.0:50058")
if err != nil {
_ = errors.New("failed to listen: the port")}
log.Print("Server started")
s := grpc.NewServer()
bds.RegisterPhoneServer(s, &phoneServer{})
reflection.Register(s)
if err = s.Serve(listener); err != nil {
log.Fatalf("failed to serve: %v", err)}
}
// Implement SendMsgBytes and SendMsgStr methods...
(Additional code for the methods would follow here, but has been omitted for brevity.)
Step 6: Verify the Server with grpcui
Once the server is running, you can verify it using grpcui. Open a new terminal window and execute:
% grpcui -plaintext 0.0.0.0:50058
This will open a browser window showing all your implemented gRPC methods.
Step 7: Unit Testing the Server
In the server folder, create a file named server_test.go to automate the verification of your server through unit tests. The complete content for server_test.go will include various test cases for SendMsgBytes and SendMsgStr.
(Refer to the original article for the complete testing code.)
Step 8: Implementing the Client
To create the client-side of the gRPC service, navigate to the client folder and create a client.go file. The client will connect to the server and send requests while receiving responses.
The main function in the client.go file will establish a connection to the server and invoke the chat methods.
package main
import (
"context"
"errors"
"io"
"log"
"time"
bds "github.com/ramseyjiang/go_mid_to_senior/pkgusages/grpc/alltypes/bidirectstream/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
cc, err := grpc.Dial("localhost:50058", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("could not connect: %v", err)}
defer cc.Close()
client := bds.NewPhoneClient(cc)
log.Println("Starting Bidirectional Stream Phone RPC...")
BidirectionalStreamSendBytesMsg(client)
BidirectionalStreamSendStrMsg(client)
}
// Implement BidirectionalStreamSendBytesMsg and BidirectionalStreamSendStrMsg methods...
(Additional code for the methods would follow here, but has been omitted for brevity.)
Step 9: Running the Client
Keep your server running and execute the following command to run the client:
% go run client/client.go
You should see the output reflecting both the client and server interactions.
Conclusion
In this article, we successfully implemented a bidirectional streaming gRPC service using Golang. I hope this guide helps you understand how to create a streaming gRPC server and client. Happy coding!
For those unfamiliar with gRPC, consider reading related articles on the topic for further learning.