Table of contents
1. RPC basics
1 - getting started with rpc
- ipc : Inter-Process Communication, inter-process communication, inter-process communication refers to the interaction between the data of two processes
- rpc : RPC is the abbreviation of remote procedure call, which is a popular communication method between different nodes in a distributed system
- RPC transport protocol
- Message serialization and deserialization
2 - Basic rpc communication
- server
package main
import (
"fmt"
"log"
"net"
"net/rpc"
)
// servuce handler
type HelloService struct {
}
// Hello的逻辑 就是 将对方发送的消息前面添加一个Hello 然后返还给对方
// 由于我们是一个rpc服务, 因此参数上面还是有约束:
//
// 第一个参数是请求
// 第二个参数是响应
//
// 可以类比Http handler
func (p *HelloService) Hello(request string, response *string) error {
*response = fmt.Sprintf("hello,%s", request)
return nil
}
func main() {
// 把我们的对象注册成一个rpc的 receiver
// 其中rpc.Register函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数
// 所有注册的方法会放在“HelloService”服务空间之下
rpc.RegisterName("HelloService", &HelloService{
})
// 然后我们建立一个唯一的TCP链接
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
// 通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务。
// 没Accept一个请求,就创建一个goroutie进行处理
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
// 前面都是tcp的知识, 到这个RPC就接管了
// 因此 你可以认为 rpc 帮我们封装消息到函数调用的这个逻辑
// 提升了工作效率, 逻辑比较简洁,可以看看他代码
go rpc.ServeConn(conn)
}
}
- client
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
// 首先是通过rpc.Dial拨号RPC服务, 建立连接
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
// 然后通过client.Call调用具体的RPC方法
// 在调用client.Call时:
// 第一个参数是用点号链接的RPC服务名字和方法名字,
// 第二个参数是 请求参数
// 第三个是请求响应, 必须是一个指针, 有底层rpc服务帮你赋值
var reply string
err = client.Call("HelloService.Hello", "jack", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
3 - Interface-based RPC service
- Optimizations that exist in the rpc method
- The client call method contains 3 parameters and 2 interface{}. You may not know what to pass in when you use it again. It’s like you wrote an HTTP service. There is no interface document, and it is easy to call errors
- In other words, how to encapsulate the rpc interface once to limit the parameter type of the interface
// Call invokes the named function, waits for it to complete, and returns its error status.
func (client *Client) Call(serviceMethod string, args interface{
}, reply interface{
}) error {
call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done
return call.Error
}
- interface\service\interface.go
package service
const (
//统一rpc的注册名
SERVICE_NAME = "HelloService"
)
type HelloService interface {
Hello(request string, response *string) error
}
- interface\server\main.go : add interface constraints
package main
import (
"fmt"
"log"
"main/interface/service"
"net"
"net/rpc"
)
// 通过接口约束HelloService服务
var _ service.HelloService = (*HelloService)(nil)
type HelloService struct {
}
func (p *HelloService) Hello(request string, response *string) error {
*response = fmt.Sprintf("hello,%s", request)
return nil
}
func main() {
rpc.RegisterName(service.SERVICE_NAME, &HelloService{
})
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
go rpc.ServeConn(conn)
}
}
- interface\client\main.go : add interface constraints
package main
import (
"fmt"
"main/interface/service"
"net/rpc"
)
// 约束客户端
var _ service.HelloService = (*HelloServiceClient)(nil)
func NewHelloServiceClient(network string, address string) (*HelloServiceClient, error) {
client, err := rpc.Dial(network, address)
if err != nil {
return nil, err
}
return &HelloServiceClient{
client: client,
}, nil
}
type HelloServiceClient struct {
client *rpc.Client
}
func (c *HelloServiceClient) Hello(request string, response *string) error {
return c.client.Call(fmt.Sprintf("%s.Hello", service.SERVICE_NAME), request, response)
}
func main() {
c, err := NewHelloServiceClient("tcp", ":1234")
if err != nil {
panic(err)
}
var response string
if err := c.Hello("tom", &response); err != nil {
panic(err)
}
fmt.Println(response)
}
Two, rpc encoding
1 - gob encoding
- What is gob encoding : RPC in the standard library defaults to the gob encoding specific to the Go language. The standard library gob is a "private" encoding and decoding method provided by golang. It is more efficient than json, xml, etc., and is especially suitable for use in the Go language. passing data between programs
- codec\gob.go
package codec
import (
"bytes"
"encoding/gob"
)
func GobEncode(obj interface{
}) ([]byte, error) {
buf := bytes.NewBuffer([]byte{
})
//编码后的结果输出到buf里
encoder := gob.NewEncoder(buf)
//编码obj对象
if err := encoder.Encode(obj); err != nil {
return []byte{
}, err
}
return buf.Bytes(), nil
}
func GobDecode(data []byte, obj interface{
}) error {
decoder := gob.NewDecoder(bytes.NewReader(data))
return decoder.Decode(obj)
}
- codec\gob_test.go
package codec
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
type TestStruct struct {
F1 string
F2 int
}
func TestGob(t *testing.T) {
should := assert.New(t)
gobBytes, err := GobEncode(&TestStruct{
F1: "test_f1",
F2: 10,
})
if should.NoError(err) {
fmt.Println(gobBytes)
}
obj := TestStruct{
}
err = GobDecode(gobBytes, &obj)
if should.NoError(err) {
fmt.Println(obj)
}
}
2 - json on tcp
- Disadvantages of gob encoding : gob is a "private" encoding and decoding method provided by golang, so it will be difficult to call RPC services implemented in Go language from other languages
- RPC framework of Go language
- When packaging RPC data, you can implement custom encoding and decoding through plug-ins
- RPC is built on the abstract io.ReadWriteCloser interface, we can build RPC on top of different communication protocols
- Some of the better encodings supported by all languages
- MessagePack: Efficient binary serialization format. It allows you to exchange data between multiple languages such as JSON. but it's faster and smaller
- JSON: text encoding
- XML: text encoding
- Protobuf binary encoding
- json_tcp\service
package service
const (
//统一rpc的注册名
SERVICE_NAME = "HelloService"
)
type HelloService interface {
Hello(request string, response *string) error
}
- json_tcp\server\main.go
package main
import (
"fmt"
"log"
"main/interface/service"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
// 通过接口约束HelloService服务
var _ service.HelloService = (*HelloService)(nil)
type HelloService struct {
}
func (p *HelloService) Hello(request string, response *string) error {
*response = fmt.Sprintf("hello,%s", request)
return nil
}
func main() {
rpc.RegisterName(service.SERVICE_NAME, &HelloService{
})
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
- json_tcp\client\main.go
package main
import (
"fmt"
"main/interface/service"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
// 约束客户端
var _ service.HelloService = (*HelloServiceClient)(nil)
func NewHelloServiceClient(network string, address string) (*HelloServiceClient, error) {
conn, err := net.Dial(network, address)
if err != nil {
return nil, err
}
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
return &HelloServiceClient{
client: client,
}, nil
}
type HelloServiceClient struct {
client *rpc.Client
}
func (c *HelloServiceClient) Hello(request string, response *string) error {
return c.client.Call(fmt.Sprintf("%s.Hello", service.SERVICE_NAME), request, response)
}
func main() {
c, err := NewHelloServiceClient("tcp", ":1234")
if err != nil {
panic(err)
}
var response string
if err := c.Hello("tom", &response); err != nil {
panic(err)
}
fmt.Println(response)
}
3 - json on http (to be added)
Three, prtotobuf encoding
1 - Overview of prtotobuf
- Protobuf overview
- Protobuf is the abbreviation of Protocol Buffers. It is a data description language developed by Google and open sourced in 2008.
- When Protobuf was first open source, its positioning was similar to data description languages such as XML and JSON. It generated code through accompanying tools and realized the function of serializing structured data.
- Protobuf, as a description language for interface specifications, can be used as a basic tool for designing secure cross-language PRC interfaces
- Reference options for codec tools
- Codec efficiency
- high compression ratio
- multilingual support
- protobuf usage process
- Simple example of protobuf
- syntax: Indicates that the syntax of proto3 is adopted. The third version of Protobuf refines and simplifies the language. All members are initialized with zero values similar to those in the Go language (custom default values are no longer supported), so message members no longer need to support the required feature.
- package: indicates that the current package is the main package (this can be consistent with the package name of Go, simplifying the example code), of course, users can also customize the corresponding package path and name for different languages
- option: some option parameters of protobuf, here specifies the path of the Go language package to be generated, other language parameters are different
- The message: keyword defines a new String type, which corresponds to a String structure in the final generated Go language code. There is only one value member of string type in the String type, and the number 1 is used instead of the name when encoding this member
syntax = "proto3";
package hello;
option go_package="gitee.com/infraboard/go-course/day21/pb";
message String {
string value = 1;
}
2 - protobuf compiler
-
Regarding data encoding :
- In data description languages such as XML or JSON, the corresponding data is generally bound by the name of the member
- However, Protobuf encoding uses the unique number of the member to bind the corresponding data, so the size of the data after Protobuf encoding will be relatively small, but it is also very inconvenient for human inspection
- We do not pay attention to the encoding technology of Protobuf at present. The final generated Go structure can freely adopt JSON or gob encoding formats, so you can temporarily ignore the member encoding part of Protobuf
-
protobuf compiler :
- Function: Compile the definition file (.proto) (IDL: Interface Description Language) into data structures in different languages
- Download address: https://github.com/protocolbuffers/protobuf/releases
- Configure protoc.exe in bin to run in environment variables ->
protoc --version
-
Common command to view the set environment variable path :
where protoc
-
include compiler library : This overwrites the corresponding include file directory as needed
-
Install the go language plugin for protobuf :
- The core tool set of Protobuf is developed in C++ language, and Go language is not supported in the official protoc compiler. To generate the corresponding Go code based on the above hello.proto file, you need to install the corresponding plug-in
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
-
Generate pb file :
protoc -I="." --go_out=./pb --go_opt=module="gitee.com/infraboard/go-course/day21/pb" pb/hello.proto
- -I: -IPATH, --proto_path=PATH, specifies the path to search for proto files, if there are multiple paths, you can use -I to specify multiple times, if not specified, the default is the current directory
- –go_out: --go refers to the name of the plugin. The plugin we installed is: protoc-gen-go, and protoc-gen is the plugin naming convention, and go is the plugin name, so here is –go, and –go_out means go The out parameter of the plug-in, here refers to the storage directory of the compiled product
- –go_opt: The opt parameter of the protoc-gen-go plugin, where the module specifies the go module, and the generated go pkg will remove the module path and generate the corresponding pkg
- pb/hello.proto: our proto file path
-
If you need to generate pb files for all current protos : go to the protobuf/pb directory and execute
protoc -I="." --go_out=. --go_opt=module="gitee.com/infraboard/go-course/day21/pb" *.proto
-
Install dependent libraries : After generation, we can see that some libraries need to be used, which can be used
go mod tidy
for synchronous installation
3 - Serialization and deserialization
- How to implement serialization and deserialization : Use the API provided by the google.golang.org/protobuf/proto tool for serialization and deserialization
- Marshal: Serialization
- UnMarshal: deserialization
- Test serialization and deserialization : protobuf\pb\hello_test.go
package pb_test
import (
"fmt"
"main/protobuf/pb"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/proto"
)
func TestMarshal(t *testing.T) {
should := assert.New(t)
str := &pb.String{
Value: "hello"}
// object -> protobuf -> []byte
pbBytes, err := proto.Marshal(str)
if should.NoError(err) {
fmt.Println(pbBytes)
}
// []byte -> protobuf -> object
obj := pb.String{
}
err = proto.Unmarshal(pbBytes, &obj)
if should.NoError(err) {
fmt.Println(obj.Value)
}
}
4 - RPC based on protobuf (to be added 00:41:00 to 01:10:00)
- Define the interactive data structure : pbrpc\service\pb\hello.proto
syntax = "proto3";
package hello;
option go_package="pbrpc/service";
message Request {
string value = 1;
}
message Response {
string value = 1;
}
- Enter the path pbrpc :
protoc -I="./service/pb/" --go_out=./service --go_opt=module="pbrpc/service" hello.proto
- pbrpc\service\interface.go
package service
const (
SERVICE_NAME = "HelloService"
)
type HelloService interface {
Hello(request *Request, response *Response) error
}
Four, proto3 syntax
1 - Message definition
- Define the message type
syntax = "proto3";
/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */
message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 result_per_page = 3;
}
- The first line is the version of protobuf, we mainly talk about the definition syntax of message
- comment: inject /* */ or //
- message_name: within the same pkg, must be unique
- filed_rule: can be absent, commonly used are repeated, oneof
- filed_type: data type, the data type defined by protobuf, the production code will be mapped to the data type of the corresponding language
- filed_name: field name, must be unique within the same message
- field_number: field number, field number when serialized into binary data, must be unique within the same message, 1 ~ 15 is represented by 1 Byte, 16 ~ 2047 is represented by 2 Bytes
<comment>
message <message_name> {
<filed_rule> <filed_type> <filed_name> = <field_number>
类型 名称 编号
}
- If you want to reserve a number for later use you can use the reserved keyword statement
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
2 - Basic type
- Value(Filed) Types : protobuf defines many Value Types, and its mapping relationship with other languages is as follows
3 - Enumerated type
- Use enum to declare enumerated types
syntax = "proto3";
package hello;
option go_package="gitee.com/infraboard/go-course/day21/pb";
enum Color {
RED = 0;
GREEN = 1;
BLACK = 2;
}
- Corresponding to the generated pb file
- Enum declaration syntax
- enum_name: enumeration name
- element_name: globally unique in pkg, very important
- element_name: must start from 0, 0 means the default value of the type, 32-bit integer
enum <enum_name> {
<element_name> = <element_number>
}
- Alias : If you do have two enumeration requirements with the same name: for example, both TaskStatus and PipelineStatus require Running, you can add one:
option allow_alias = true;
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
- Enums also support reserved values
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
4 - Array type
- The array type uses repeated
syntax = "proto3";
package hello;
option go_package="protobuf/pb";
message Host{
string name = 1;
string ip = 2;
}
message SearchResponse {
int64 total = 1;
//定义一个HOST数组
repeated Host hosts = 2;
}
5 - Map
- The syntax of protobuf declaration map :
map<key_type, value_type> map_field = N;
syntax = "proto3";
package hello;
option go_package="protobuf/pb";
message Host{
string name = 1;
string ip = 2;
map <string,string> tags = 3;
}
message SearchResponse {
int64 total = 1;
//定义一个HOST数组
repeated Host hosts = 2;
}
6 - Oneof
- Oneof : The limited type can only be one of them
message ProtobufEventHeader{
string id = 1;
map<string,string> headers = 2;
}
message JSONEventHeader{
string id = 1;
bytes headers = 2;
}
message Event{
oneof header{
ProtobufEventHeader protobuf = 1;
JSONEventHeader json = 2;
}
}
- two ways to use
- Use assertions to determine the type of one of:
e.GetHeader()
- Obtain directly through get to determine whether the return is nil
err1 := e.GetProtobuf()
err2 := e.GetJson()
- Use assertions to determine the type of one of:
7 - Any
- Any : When we cannot clearly define the data type, we can use Any to represent
- Note that the package needs to be imported:
import "google/protobuf/any.proto"
- If protoc reports an error, you need to specify the path of google protobuf in -I
- Note that the package needs to be imported:
// 这里是应用其他的proto文件, 后面会讲 ipmort用法
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
func TestAny(t *testing.T) {
es := &pb.ErrorStatus{
Message: "hello"}
anyEs, err := anypb.New(es)
if err != nil {
panic("err")
}
fmt.Println(anyEs) //[type.googleapis.com/hello.ErrorStatus]:{message:"hello"}
obj := pb.ErrorStatus{
}
anyEs.UnmarshalTo(&obj)
fmt.Println(obj.Message) //hello
}
8 - Type nesting
- Message can be nested in message (not recommended) : but anonymous nesting is not allowed, field name must be specified
message Outer {
// Level 0
message MiddleAA {
// Level 1
message Inner {
// Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB {
// Level 1
message Inner {
// Level 2
int32 ival = 1;
bool booly = 2;
}
}
}
9 - Reference package
- import “google/protobuf/any.proto”;:
- The above is the standard library to be read in this case. When we installed protoc, we have moved the changed lib to usr/local/include, so we can find
- If our proto file is not in the /usr/local/include directory, we can add the search path through -I, so that the compiler can find the package we imported
protoc -I=. --go_out=./pb --go_opt=module="gitee.com/infraboard/go-course/day21/pb" pb/import.proto
5. gRPC
1 - gRPC concept
-
What is grpc : gRPC is a cross-language open source RPC framework developed by Google based on Protobuf. gRPC is designed based on the HTTP/2 protocol and can provide multiple services based on one HTTP/2 link, which is more friendly to mobile devices
-
GRPC technology stack : Stub: The application communicates with the gRPC core library through the Stub code produced by the gRPC plug-in, or directly communicates with the gRPC core library
- Data interaction format: protobuf
- Communication method: The bottom layer is TCP or Unix Socket protocol, above which is the implementation of HTTP/2 protocol
- Core library: On top of the HTTP/2 protocol, a gRPC core library for the Go language is built
- Stub: The application communicates with the gRPC core library through the Stub code produced by the gRPC plug-in, or directly communicates with the gRPC core library
-
Install the grpc plugin :
# protoc-gen-go 插件之前已经安装
# go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
# 安装protoc-gen-go-grpc插件
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
- View the current version of the plugin : ``
2 - Simple implementation of gRPC
- grpc\server\pb\hello.proto
- Enter the corresponding path according to the requirement: C:\develop_project\go_project\proj1\grpc\server
- Build command:
protoc -I="." --go_out=. --go_opt=module="grpc/server" --go-grpc_out=. --go-grpc_opt=module="grpc/server" pb/hello.proto
- You can see that grpc\server\pb\hello_grpc.pb.go is generated
- If it is the first generation, it needs to be synchronized:
go mod tidy
syntax = "proto3";
package hello;
option go_package="grpc/server/pb";
//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" pb/hello.proto
//grpc生成
//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" --go-grpc_out=. --go-grpc_opt=module="grpc/server" pb/hello.proto
service HelloService{
rpc hello (HelloRequest) returns(HelloResponse);
}
message HelloRequest{
string value = 1;
}
message HelloResponse{
string value = 1;
}
- server:grpc\server\main.go
package main
import (
"context"
"fmt"
"log"
"main/grpc/server/pb"
"net"
"google.golang.org/grpc"
)
type HelloServiceServer struct {
pb.UnimplementedHelloServiceServer
}
func (p *HelloServiceServer) Hello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{
Value: fmt.Sprintf("hello,%s", req.Value),
}, nil
}
func main() {
//把实现类注册给 GRPC Server
server := grpc.NewServer()
pb.RegisterHelloServiceServer(server, &HelloServiceServer{
})
listen, err := net.Listen("tcp", ":1234")
if err != nil {
panic(err)
}
log.Printf("grpc listen addr: 127.0.0.1:1234")
//监听Socket,HTTP2内置
if err := server.Serve(listen); err != nil {
panic(err)
}
}
- client:grpc\client\main.go
package main
import (
"context"
"fmt"
"main/grpc/server/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
//第一步,建立网络连接
conn, err := grpc.DialContext(context.Background(), "127.0.0.1:1234", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
//grpc为我们生成一个客户端调用服务端的SDK
client := pb.NewHelloServiceClient(conn)
resp, err := client.Hello(context.Background(), &pb.HelloRequest{
Value: "tom"})
if err != nil {
panic(err)
}
fmt.Println(resp.Value)
}
3 - gRPC stream
- Why use an RPC stream :
- RPC is a remote function call, so the function parameters and return value of each call cannot be too large, otherwise it will seriously affect the response time of each call
- Therefore, the traditional RPC method call is not suitable for uploading and downloading large data volume scenarios
- To this end, the gRPC framework provides streaming features for the server and client respectively
- How to formulate a gRPC stream :
- The keyword stream specifies to enable the stream feature, the parameter part is the stream receiving the client parameters, and the return value is the stream returned to the client
- The syntax for defining a gRPC stream:
rpc <function_name> (stream <type>) returns (stream <type>) {}
- grpc\server\pb\hello.proto
syntax = "proto3";
package hello;
option go_package="grpc/server/pb";
//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" pb/hello.proto
//grpc生成
//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" --go-grpc_out=. --go-grpc_opt=module="grpc/server" pb/hello.proto
service HelloService{
rpc Channel(stream HelloRequest) returns(stream HelloResponse){
}
}
message HelloRequest{
string value = 1;
}
message HelloResponse{
string value = 1;
}
- grpc\server\main.go
package main
import (
"fmt"
"io"
"log"
"main/grpc/server/pb"
"net"
"google.golang.org/grpc"
)
type HelloServiceServer struct {
pb.UnimplementedHelloServiceServer
}
func (p *HelloServiceServer) Channel(stream pb.HelloService_ChannelServer) error {
for {
//接收请求
req, err := stream.Recv()
if err != nil {
if err == io.EOF {
//当前客户端退出
log.Printf("client closed")
return nil
}
return err
}
fmt.Printf("recv req value : %s\n", req.Value)
resp := &pb.HelloResponse{
Value: fmt.Sprintf("hello,%s", req.Value)}
//响应请求
err = stream.Send(resp)
if err != nil {
if err == io.EOF {
log.Printf("client closed")
return nil
}
return err //服务端发送异常, 函数退出, 服务端流关闭
}
}
}
func main() {
//把实现类注册给 GRPC Server
server := grpc.NewServer()
pb.RegisterHelloServiceServer(server, &HelloServiceServer{
})
listen, err := net.Listen("tcp", ":1234")
if err != nil {
panic(err)
}
log.Printf("grpc listen addr: 127.0.0.1:1234")
//监听Socket,HTTP2内置
if err := server.Serve(listen); err != nil {
panic(err)
}
}
- grpc\client\main.go
package main
import (
"context"
"fmt"
"main/grpc/server/pb"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
//第一步,建立网络连接
conn, err := grpc.DialContext(context.Background(), "127.0.0.1:1234", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
//grpc为我们生成一个客户端调用服务端的SDK
client := pb.NewHelloServiceClient(conn)
stream, err := client.Channel(context.Background())
if err != nil {
panic(err)
}
//启用一个Goroutine来发送请求
go func() {
for {
err := stream.Send((&pb.HelloRequest{
Value: "tom"}))
if err != nil {
panic(err)
}
time.Sleep(1 * time.Second)
}
}()
for {
//主循环,负责接收服务端响应
resp, err := stream.Recv()
if err != nil {
panic(err)
}
fmt.Println(resp)
}
}