Go语言与gRpc

1. gRpc 概述

gRpc是什么 ?

gRPC是Google开源的可以在任何环境中运行的现代开源高性能RPC框架。它可以通过可插拔的支持来有效地连接数据中心内和跨数据中心的服务,以实现负载平衡,跟踪,健康检查和身份验证。它也适用于分布式计算的最后一英里,以将设备,移动应用程序和浏览器连接到后端服务。

主要使用场景

  1. 在微服务风格架构中有效连接多语种服务
  2. 将移动设备,浏览器客户端连接到后端服务
  3. 生成高效的客户端库

gRpc官方地址

https://grpc.io/

gRpc源码托管地址

https://github.com/grpc/grpc

gRpc支持我们常见的编程语言(C++ java Python Go Ruby C# Node.js PHP Dart Objective-C ) ,这些编程语言基本都有对gRpc的实现

详情可以参看 https://grpc.io/docs/

2. gRpc执行概述

在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

grpc.png

通过任意编程语言创建的gRpc服务端和客户端可以运行在多种环境中,创建的gRpc服务端,可以通过任意编程语言编写的客户端调用

gRpc默认使用protocol buffers 对数据进行序列化

关于Protobuf的详情,请参看 https://developers.google.com/protocol-buffers

3. gRpc-go 安装

gRpc 有多种语言的实现

  • C++: follow the instructions under the src/cpp directory
  • C#: NuGet package Grpc
  • Dart: pub package grpc
  • Go: go get google.golang.org/grpc
  • Java: Use JARs from Maven Central Repository
  • Node: npm install grpc
  • Objective-C: Add gRPC-ProtoRPC dependency to podspec
  • PHP: pecl install grpc
  • Python: pip install grpcio
  • Ruby: gem install grpc
  • WebJS: follow the grpc-web instructions
Language Source
Shared C [core library] src/core
C++ src/cpp
Ruby src/ruby
Python src/python
PHP src/php
C# (core library based) src/csharp
Objective-C src/objective-c
Java grpc-java
Go grpc-go
NodeJS grpc-node
WebJS grpc-web
Dart grpc-dart
.NET (pure C# impl.) grpc-dotnet

grpc- go 是gRpc库的Golang 实现版本,也是我们需要安装的版本(根据自己的开发语言选择安装)

安装细节

进入GOPATH目录下执行如下命令

go get -u google.golang.org/grpc

不出意外的话会安装失败,报错信息如下:

package google.golang.org/grpc: unrecognized import path "google.golang.org/grpc" (https fetch: Get https://google.golang.org/grpc?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)

这怎么办呢?来来换个姿势继续操作

下载 grpc-go 的源码包和依赖包

git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto

下载完成之后 ,执行安装命令

cd $GOPATH/src
go install google.golang.org/grpc

因为grpc默认使用Protocol buffers

所以必须安装 Protocol Buffers 编译器,安装步骤省略

$ protoc --version
libprotoc 3.11.0

我们开发语言是Golang,所以也需要安装protobuf文件的Go语言代码生成插件

go get github.com/golang/protobuf/protoc-gen-go 

4. gRpc使用

上面的基础环境具备之后,我们就可以快乐的使用gRpc开发服务了

我们梳理一下gRpc框架使用的最基础的流程:

  1. 编写Protobuf 文件,在Protobuf文件中定义服务和接口

  2. 自动生成Go语言代码.

    这里主要是指安装的protoc-gen-go 插件然后指定参数生成兼容gRpc框架的Go语言代码

  3. 服务接口的实现

  4. gRpc服务端实现

  5. gRpc客户端实现

在gRpc安装完成之后会有如下的一个目录存在

$GOPATH/src/google.golang.org/grpc/examples

gRpc的很多功能的使用示例都在其中,我们学习gRpc可以多看看这个目录中的文件

step1 : 编写一个.proto 文件,文件名自己拟定

例如: demo5.proto

syntax = "proto3";
package example;
// 添加服务
service Demo5 {
    // 定义服务接口
    rpc GetInfo (Demo5Request) returns (Demo5Response) {
    }
    rpc SetName (Demo5Request) returns (Demo5Response) {
    }
}
message Demo5Request {
    string name = 1;
    int32 age = 2;
    enum Gender {
        MALE = 0;
        FEMALE = 1;
    }
    message Other {
        string addr = 1;
        string hobby = 2;
        Gender g = 3;
    }
    Other info = 3;
}
message Demo5Response {
    string info = 1;
}

step2 : 自动生成Go语言代码

执行完下面的命令,将生成代码文件 demo5.pb.go

 protoc --go_out=plugins=grpc:. example/demo5.proto

demo5.pb.go 具体内容如下(在开发中不太会去关注其中的内容)

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: example/demo5.proto

package example

import (
    context "context"
    fmt "fmt"
    proto "github.com/golang/protobuf/proto"
    grpc "google.golang.org/grpc"
    codes "google.golang.org/grpc/codes"
    status "google.golang.org/grpc/status"
    math "math"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package

type Demo5Request_Gender int32

const (
    Demo5Request_MALE   Demo5Request_Gender = 0
    Demo5Request_FEMALE Demo5Request_Gender = 1
)

var Demo5Request_Gender_name = map[int32]string{
    0: "MALE",
    1: "FEMALE",
}

var Demo5Request_Gender_value = map[string]int32{
    "MALE":   0,
    "FEMALE": 1,
}

func (x Demo5Request_Gender) String() string {
    return proto.EnumName(Demo5Request_Gender_name, int32(x))
}

func (Demo5Request_Gender) EnumDescriptor() ([]byte, []int) {
    return fileDescriptor_dc43dfb84d83bd6d, []int{0, 0}
}

type Demo5Request struct {
    Name                 string              `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Age                  int32               `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
    Info                 *Demo5Request_Other `protobuf:"bytes,3,opt,name=info,proto3" json:"info,omitempty"`
    XXX_NoUnkeyedLiteral struct{}            `json:"-"`
    XXX_unrecognized     []byte              `json:"-"`
    XXX_sizecache        int32               `json:"-"`
}

func (m *Demo5Request) Reset()         { *m = Demo5Request{} }
func (m *Demo5Request) String() string { return proto.CompactTextString(m) }
func (*Demo5Request) ProtoMessage()    {}
func (*Demo5Request) Descriptor() ([]byte, []int) {
    return fileDescriptor_dc43dfb84d83bd6d, []int{0}
}

func (m *Demo5Request) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_Demo5Request.Unmarshal(m, b)
}
func (m *Demo5Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_Demo5Request.Marshal(b, m, deterministic)
}
func (m *Demo5Request) XXX_Merge(src proto.Message) {
    xxx_messageInfo_Demo5Request.Merge(m, src)
}
func (m *Demo5Request) XXX_Size() int {
    return xxx_messageInfo_Demo5Request.Size(m)
}
func (m *Demo5Request) XXX_DiscardUnknown() {
    xxx_messageInfo_Demo5Request.DiscardUnknown(m)
}

var xxx_messageInfo_Demo5Request proto.InternalMessageInfo

func (m *Demo5Request) GetName() string {
    if m != nil {
        return m.Name
    }
    return ""
}

func (m *Demo5Request) GetAge() int32 {
    if m != nil {
        return m.Age
    }
    return 0
}

func (m *Demo5Request) GetInfo() *Demo5Request_Other {
    if m != nil {
        return m.Info
    }
    return nil
}

type Demo5Request_Other struct {
    Addr                 string              `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"`
    Hobby                string              `protobuf:"bytes,2,opt,name=hobby,proto3" json:"hobby,omitempty"`
    G                    Demo5Request_Gender `protobuf:"varint,3,opt,name=g,proto3,enum=example.Demo5Request_Gender" json:"g,omitempty"`
    XXX_NoUnkeyedLiteral struct{}            `json:"-"`
    XXX_unrecognized     []byte              `json:"-"`
    XXX_sizecache        int32               `json:"-"`
}

func (m *Demo5Request_Other) Reset()         { *m = Demo5Request_Other{} }
func (m *Demo5Request_Other) String() string { return proto.CompactTextString(m) }
func (*Demo5Request_Other) ProtoMessage()    {}
func (*Demo5Request_Other) Descriptor() ([]byte, []int) {
    return fileDescriptor_dc43dfb84d83bd6d, []int{0, 0}
}

func (m *Demo5Request_Other) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_Demo5Request_Other.Unmarshal(m, b)
}
func (m *Demo5Request_Other) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_Demo5Request_Other.Marshal(b, m, deterministic)
}
func (m *Demo5Request_Other) XXX_Merge(src proto.Message) {
    xxx_messageInfo_Demo5Request_Other.Merge(m, src)
}
func (m *Demo5Request_Other) XXX_Size() int {
    return xxx_messageInfo_Demo5Request_Other.Size(m)
}
func (m *Demo5Request_Other) XXX_DiscardUnknown() {
    xxx_messageInfo_Demo5Request_Other.DiscardUnknown(m)
}

var xxx_messageInfo_Demo5Request_Other proto.InternalMessageInfo

func (m *Demo5Request_Other) GetAddr() string {
    if m != nil {
        return m.Addr
    }
    return ""
}

func (m *Demo5Request_Other) GetHobby() string {
    if m != nil {
        return m.Hobby
    }
    return ""
}

func (m *Demo5Request_Other) GetG() Demo5Request_Gender {
    if m != nil {
        return m.G
    }
    return Demo5Request_MALE
}

type Demo5Response struct {
    Info                 string   `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *Demo5Response) Reset()         { *m = Demo5Response{} }
func (m *Demo5Response) String() string { return proto.CompactTextString(m) }
func (*Demo5Response) ProtoMessage()    {}
func (*Demo5Response) Descriptor() ([]byte, []int) {
    return fileDescriptor_dc43dfb84d83bd6d, []int{1}
}

func (m *Demo5Response) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_Demo5Response.Unmarshal(m, b)
}
func (m *Demo5Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_Demo5Response.Marshal(b, m, deterministic)
}
func (m *Demo5Response) XXX_Merge(src proto.Message) {
    xxx_messageInfo_Demo5Response.Merge(m, src)
}
func (m *Demo5Response) XXX_Size() int {
    return xxx_messageInfo_Demo5Response.Size(m)
}
func (m *Demo5Response) XXX_DiscardUnknown() {
    xxx_messageInfo_Demo5Response.DiscardUnknown(m)
}

var xxx_messageInfo_Demo5Response proto.InternalMessageInfo

func (m *Demo5Response) GetInfo() string {
    if m != nil {
        return m.Info
    }
    return ""
}

func init() {
    proto.RegisterEnum("example.Demo5Request_Gender", Demo5Request_Gender_name, Demo5Request_Gender_value)
    proto.RegisterType((*Demo5Request)(nil), "example.Demo5Request")
    proto.RegisterType((*Demo5Request_Other)(nil), "example.Demo5Request.Other")
    proto.RegisterType((*Demo5Response)(nil), "example.Demo5Response")
}

func init() { proto.RegisterFile("example/demo5.proto", fileDescriptor_dc43dfb84d83bd6d) }

var fileDescriptor_dc43dfb84d83bd6d = []byte{
    // 271 bytes of a gzipped FileDescriptorProto
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0xad, 0x48, 0xcc,
    0x2d, 0xc8, 0x49, 0xd5, 0x4f, 0x49, 0xcd, 0xcd, 0x37, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
    0x62, 0x87, 0x0a, 0x2a, 0x3d, 0x61, 0xe4, 0xe2, 0x71, 0x01, 0x49, 0x04, 0xa5, 0x16, 0x96, 0xa6,
    0x16, 0x97, 0x08, 0x09, 0x71, 0xb1, 0xe4, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70,
    0x06, 0x81, 0xd9, 0x42, 0x02, 0x5c, 0xcc, 0x89, 0xe9, 0xa9, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xac,
    0x41, 0x20, 0xa6, 0x90, 0x3e, 0x17, 0x4b, 0x66, 0x5e, 0x5a, 0xbe, 0x04, 0xb3, 0x02, 0xa3, 0x06,
    0xb7, 0x91, 0xb4, 0x1e, 0xd4, 0x38, 0x3d, 0x64, 0xa3, 0xf4, 0xfc, 0x4b, 0x32, 0x52, 0x8b, 0x82,
    0xc0, 0x0a, 0xa5, 0x62, 0xb9, 0x58, 0xc1, 0x5c, 0x90, 0xf9, 0x89, 0x29, 0x29, 0x45, 0x30, 0xf3,
    0x41, 0x6c, 0x21, 0x11, 0x2e, 0xd6, 0x8c, 0xfc, 0xa4, 0xa4, 0x4a, 0xb0, 0x0d, 0x9c, 0x41, 0x10,
    0x8e, 0x90, 0x16, 0x17, 0x63, 0x3a, 0xd8, 0x02, 0x3e, 0x23, 0x19, 0xec, 0x16, 0xb8, 0xa7, 0xe6,
    0xa5, 0xa4, 0x16, 0x05, 0x31, 0xa6, 0x2b, 0xc9, 0x71, 0xb1, 0x41, 0x38, 0x42, 0x1c, 0x5c, 0x2c,
    0xbe, 0x8e, 0x3e, 0xae, 0x02, 0x0c, 0x42, 0x5c, 0x5c, 0x6c, 0x6e, 0xae, 0x60, 0x36, 0xa3, 0x92,
    0x32, 0x17, 0x2f, 0x54, 0x67, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0x2a, 0xc8, 0x19, 0x60, 0x0f, 0x40,
    0x9d, 0x01, 0x62, 0x1b, 0xd5, 0x73, 0xb1, 0x82, 0x15, 0x09, 0x59, 0x71, 0xb1, 0xbb, 0xa7, 0x96,
    0x78, 0xe6, 0xa5, 0xe5, 0x0b, 0x89, 0x62, 0xb5, 0x59, 0x4a, 0x0c, 0x5d, 0x18, 0x62, 0xac, 0x12,
    0x03, 0x48, 0x6f, 0x70, 0x6a, 0x89, 0x1f, 0x28, 0xd8, 0x48, 0xd5, 0x9b, 0xc4, 0x06, 0x8e, 0x1c,
    0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x16, 0x42, 0x94, 0xe0, 0xb3, 0x01, 0x00, 0x00,
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4

// Demo5Client is the client API for Demo5 service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type Demo5Client interface {
    GetInfo(ctx context.Context, in *Demo5Request, opts ...grpc.CallOption) (*Demo5Response, error)
    SetName(ctx context.Context, in *Demo5Request, opts ...grpc.CallOption) (*Demo5Response, error)
}

type demo5Client struct {
    cc *grpc.ClientConn
}

func NewDemo5Client(cc *grpc.ClientConn) Demo5Client {
    return &demo5Client{cc}
}

func (c *demo5Client) GetInfo(ctx context.Context, in *Demo5Request, opts ...grpc.CallOption) (*Demo5Response, error) {
    out := new(Demo5Response)
    err := c.cc.Invoke(ctx, "/example.Demo5/GetInfo", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

func (c *demo5Client) SetName(ctx context.Context, in *Demo5Request, opts ...grpc.CallOption) (*Demo5Response, error) {
    out := new(Demo5Response)
    err := c.cc.Invoke(ctx, "/example.Demo5/SetName", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// Demo5Server is the server API for Demo5 service.
type Demo5Server interface {
    GetInfo(context.Context, *Demo5Request) (*Demo5Response, error)
    SetName(context.Context, *Demo5Request) (*Demo5Response, error)
}

// UnimplementedDemo5Server can be embedded to have forward compatible implementations.
type UnimplementedDemo5Server struct {
}

func (*UnimplementedDemo5Server) GetInfo(ctx context.Context, req *Demo5Request) (*Demo5Response, error) {
    return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
}
func (*UnimplementedDemo5Server) SetName(ctx context.Context, req *Demo5Request) (*Demo5Response, error) {
    return nil, status.Errorf(codes.Unimplemented, "method SetName not implemented")
}

func RegisterDemo5Server(s *grpc.Server, srv Demo5Server) {
    s.RegisterService(&_Demo5_serviceDesc, srv)
}

func _Demo5_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(Demo5Request)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(Demo5Server).GetInfo(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: "/example.Demo5/GetInfo",
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(Demo5Server).GetInfo(ctx, req.(*Demo5Request))
    }
    return interceptor(ctx, in, info, handler)
}

func _Demo5_SetName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(Demo5Request)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(Demo5Server).SetName(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: "/example.Demo5/SetName",
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(Demo5Server).SetName(ctx, req.(*Demo5Request))
    }
    return interceptor(ctx, in, info, handler)
}

var _Demo5_serviceDesc = grpc.ServiceDesc{
    ServiceName: "example.Demo5",
    HandlerType: (*Demo5Server)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "GetInfo",
            Handler:    _Demo5_GetInfo_Handler,
        },
        {
            MethodName: "SetName",
            Handler:    _Demo5_SetName_Handler,
        },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: "example/demo5.proto",
}

step3 : gRpc服务端实现

具体服务的实现代码可以单独放在一个文件中,此处我们都放在了gRpc服务端代码文件中

文件名: grpc_demo5_server.go

package main

import (
    pb "GoNote/chapter10/demo9/example"
    "fmt"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "log"
    "net"
)

// 实现服务
type Demo5Server struct {
    pb.UnimplementedDemo5Server
}
// 实现服务定义的接口
func (d *Demo5Server) GetInfo(ctx context.Context, in *pb.Demo5Request) (*pb.Demo5Response, error) {
    InfoS := fmt.Sprintf("my name is %s, i am %d, i live in %s", in.Name, in.Age, in.Info.Addr)
    return &pb.Demo5Response{Info: InfoS}, nil
}
func (d *Demo5Server) SetName(ctx context.Context, in *pb.Demo5Request) (*pb.Demo5Response, error) {
    return &pb.Demo5Response{Info: "getName"}, nil
}
func main() {
    // 监听8080端口
    listen, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal("failed to listen : ", err.Error())
    }
    // 创建一个没有注册服务的新的gRpc服务器
    s := grpc.NewServer()
    // 注册服务
    pb.RegisterDemo5Server(s, &Demo5Server{})
    fmt.Println("gRpc 服务端开启")
    // 接收gRpc请求
    if err := s.Serve(listen); err != nil {
        log.Fatal("this is error : ", err.Error())
    }
}

step4 : gRpc客户端实现

文件名: grpc_demo5_client.go

package main

import (
    pb "GoNote/chapter10/demo9/example"
    "fmt"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "log"
    "time"
)

func main() {
    // 创建一个客户端连接
    conn, err := grpc.Dial(":8080", grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        log.Fatal("connect failed : ", err.Error())
    }
    // 关闭客户端连接
    defer conn.Close()
    // 客户端服务api
    client := pb.NewDemo5Client(conn)
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
    defer cancel()
    // 客户端请求数据
    req := pb.Demo5Request{
        Name: "Tom",
        Age:  99,
        Info: &pb.Demo5Request_Other{
            Addr:  "Beijing",
            Hobby: "climbing",
            G:     0,
        },
    }
    // 调用服务接口
    r, err := client.GetInfo(ctx, &req)
    if err != nil {
        log.Fatal("called failed : ", err.Error())
    }
    // 打印结果
    fmt.Println(r.Info)
}

测试

$ go run grpc_demo5_server.go
gRpc 服务端开启
$ go run grpc_demo5_client.go
my name is Tom, i am 99, i live in Beijing
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • gRPC 是一个高性能、通用的开源RPC框架,基于HTTP/2协议标准和Protobuf序列化协议开发,支持众多的...
    小波同学阅读 18,970评论 6 19
  • 原文出处:gRPC gRPC分享 概述 gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远...
    小波同学阅读 7,131评论 0 18
  • 1.简介 在gRPC中,客户端应用程序可以直接调用不同计算机上的服务器应用程序上的方法,就像它是本地对象一样,使您...
    第八共同体阅读 1,812评论 0 6
  • 凡事有所保留,此法能确保你在他人心中的重要性,任何时候你都不能一下用尽你所有的才能和力量。 知道怎样才能依靠自己。...
    哲与思阅读 31评论 0 1
  • 昨晚我和先生第一次睡觉时关上卧室门,感觉真的挺不错。先生不再因孩子夜里有动静而惊醒,影响先生休息;而孩子也因为我们...
    修静气阅读 120评论 0 2