Basics tutorial를 기반으로 작성했다. 아래에서 gRPC 클라이언트 부분에 관해서만 작성한다.
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative
syntax = "proto3";
package chat;
option go_package = "gRPC-based-chatting/chatProto;chatProto";
import "google/protobuf/timestamp.proto";
message ChatMessage {
string channel = 1; // 채널 (채팅방 ID)
string sender = 2; // 송신자
string receiver = 3; // 수신자
string content = 4; // 내용
google.protobuf.Timestamp timestamp = 5; // 타임스탬프
}
service ChatService {
rpc ChatStream(stream ChatMessage) returns (stream ChatMessage);
}
type ChatServiceClient interface {
// NewChatServiceClient의 반환 타입
ChatStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ChatMessage, ChatMessage], error)
}
type chatServiceClient struct {
cc grpc.ClientConnInterface
}
func NewChatServiceClient(cc grpc.ClientConnInterface) ChatServiceClient {
// gRPC 서버와의 연결 객체(ClientConnInterface)를 받아 chatServiceClient 구조체를 생성
// 생성된 구조체를 인터페이스 타입(ChatServiceClient)으로 반환
return &chatServiceClient{cc}
}
func (c *chatServiceClient) ChatStream(
ctx context.Context,
opts ...grpc.CallOption
) (grpc.BidiStreamingClient[ChatMessage, ChatMessage], error) {
// 양방향 스트림 생성 메소드
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &ChatService_ServiceDesc.Streams[0], ChatService_ChatStream_FullMethodName, cOpts...)
if err != nil { // 에러 체크
return nil, err
}
x := &grpc.GenericClientStream[ChatMessage, ChatMessage]{ClientStream: stream}
return x, nil
}
type ChatService_ChatStreamClient = grpc.BidiStreamingClient[ChatMessage, ChatMessage]
// ChatStream의 반환 타입
NewChatServiceClient 메소드를 통해 gRPC 클라이언트를 생성.proto 파일에서 정의한 service의 메소드를 호출ChatStream을 통해 gRPC 클라이언트가 gRPC 서버와 통신할 스트림을 반환ChatStream은 .proto 파일에서 정의한 service의 메소드context와 통신 옵션인 CallOption을 전달NewChatServiceClient의 반환 값 클라이언트는 ChatServiceClient type으로 정의ChatStream의 반환 값 스트림은 ChatService_ChatStreamClient type으로 정의.proto 코드를 컴파일해 자동 생성된 pb.go와 grpc.pb.go를 참고해 사용자가 작성해야 하는 파일type Client struct {
conn *grpc.ClientConn
client pb.ChatServiceClient
streams map[string]pb.ChatService_ChatStreamClient
ctx context.Context
}
// gRPC 서버에 연결 및 새로운 클라이언트 생성
func NewClient() (*Client, error) {
// gRPC 서버에 연결
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Printf("에러 발생: %v", err)
return nil, err
}
// 클라이언트 생성
client := pb.NewChatServiceClient(conn)
log.Printf("클라이언트 생성 완료")
// 컨텍스트 생성
ctx := context.Background()
return &Client{
conn: conn,
client: client,
streams: make(map[string]pb.ChatService_ChatStreamClient),
ctx: ctx,
}, nil
}
// 양방향 스트리밍 시작
func (c *Client) StartChat(chanId string) error {
if c.streams[chanId] != nil {
log.Printf("스트림이 이미 열려 있음")
return nil
}
// 스트림 생성
stream, err := c.client.ChatStream(c.ctx)
if err != nil {
log.Printf("스트림 생성 실패: %v", err)
return err
}
c.streams[chanId] = stream
log.Printf("스트림 생성 완료")
// ...
return nil
}
Client로 생성했다.Client 구조체에 포함되도록 했다.