热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Go实战golang中使用gRPC和Protobuf实现高性能api(golang/protobuf、google.golang.org/grpc)

Go实战--golang中使用gRPC和Protobuf实现高性能api(golangprotobuf、google.golang.orggrpc),Go语言社区,Golang程序

生命不止,继续 go go go !!!

号外号外,插播一条广告,通过博客的uv可以看到周五,程序员是不怎么干活的:
这里写图片描述

本篇博客,使用gRPC和Protobuf,实现所谓的高性能api。

protobuf

golang中的protobuf大家应该不会很陌生,之前也有博客介绍过:
Go实战–go中使用google/protobuf(The way to go)

Protocol Buffers (a.k.a., protobuf) are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data. You can find protobuf’s documentation on the Google Developers site.

获取:

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

Protobuf语法
下面简要介绍Protobuf语法:
参考:https://segmentfault.com/a/1190000007917576
官方:
https://developers.google.com/protocol-buffers/
https://developers.google.com/protocol-buffers/docs/gotutorial

Message定义
一个message类型定义描述了一个请求或相应的消息格式,可以包含多种类型字段。例如定义一个搜索请求的消息格式,每个请求包含查询字符串、页码、每页数目。

syntax = "proto3";
package tutorial;

首行声明使用的protobuf版本为proto3

message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phOnes= 4;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}

生成

protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

写消息

book := &pb.AddressBook{}
// ...

// Write the new address book back to disk.
out, err := proto.Marshal(book)
if err != nil {
        log.Fatalln("Failed to encode address book:", err)
}
if err := ioutil.WriteFile(fname, out, 0644); err != nil {
        log.Fatalln("Failed to write address book:", err)
}

读消息

// Read the existing address book.
in, err := ioutil.ReadFile(fname)
if err != nil {
        log.Fatalln("Error reading file:", err)
}
book := &pb.AddressBook{}
if err := proto.Unmarshal(in, book); err != nil {
        log.Fatalln("Failed to parse address book:", err)
}

grpc

golang中的rpc大家也不会陌生,之前也有介绍过奥:
Go实战–go中使用rpc(The way to go)

什么是rpc
RPC是Remote Procedure CallProtocol的缩写,即—远程过程调用协议。

RPC是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用,信息数据。通过它可以使函数调用模式网络化。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

net/rpc
Go语言标准库能够自带一个rpc框架还是非常给力的,这可以很大程度的降低写后端网络通信服务的门槛,特别是在大规模的分布式系统中,rpc基本是跨机器通信的标配。rpc能够最大程度屏蔽网络细节,让开发者专注在服务功能的开发上面

什么是grpc
gRPC 是一个高性能、开源、通用的RPC框架,由Google推出,基于HTTP/2协议标准设计开发,默认采用Protocol Buffers数据序列化协议,支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库。

在gRPC客户端可以直接调用不同服务器上的远程程序,使用姿势看起来就像调用本地程序一样,很容易去构建分布式应用和服务。和很多RPC系统一样,服务端负责实现定义好的接口并处理客户端的请求,客户端根据接口描述直接调用需要的服务。客户端和服务端可以分别使用gRPC支持的不同语言实现。

这里写图片描述

grpc-go
github地址:
https://github.com/grpc/grpc-go

Star: 4402

文档地址:
https://godoc.org/google.golang.org/grpc

获取:
go get -u google.golang.org/grpc

应用

文件结构
这里写图片描述

proto文件
customer.proto

syntax = "proto3";
package customer;


// The Customer service definition.
service Customer {   
  // Get all Customers with filter - A server-to-client streaming RPC.
  rpc GetCustomers(CustomerFilter) returns (stream CustomerRequest) {}
  // Create a new Customer - A simple RPC 
  rpc CreateCustomer (CustomerRequest) returns (CustomerResponse) {}
}

// Request message for creating a new customer
message CustomerRequest {
  int32 id = 1;  // Unique ID number for a Customer.
  string name = 2;
  string email = 3;
  string phOne= 4;

  message Address {
    string street = 1;
    string city = 2;
    string state = 3;
    string zip = 4;
    bool isShippingAddress = 5; 
  }

  repeated Address addresses = 5;
}

message CustomerResponse {
  int32 id = 1;
  bool success = 2;
}
message CustomerFilter {    
  string keyword = 1;
}

生成customer.pb.go

protoc --go_out=plugins=grpc:. customer.proto
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: customer.proto

/*
Package customer is a generated protocol buffer package.

It is generated from these files:
    customer.proto

It has these top-level messages:
    CustomerRequest
    CustomerResponse
    CustomerFilter
*/
package customer

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

import (
    context "golang.org/x/net/context"
    grpc "google.golang.org/grpc"
)

// 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.ProtoPackageIsVersion2 // please upgrade the proto package

// Request message for creating a new customer
type CustomerRequest struct {
    Id        int32                      `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
    Name      string                     `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
    Email     string                     `protobuf:"bytes,3,opt,name=email" json:"email,omitempty"`
    Phone     string                     `protobuf:"bytes,4,opt,name=phone" json:"phone,omitempty"`
    Addresses []*CustomerRequest_Address `protobuf:"bytes,5,rep,name=addresses" json:"addresses,omitempty"`
}

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

func (m *CustomerRequest) GetId() int32 {
    if m != nil {
        return m.Id
    }
    return 0
}

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

func (m *CustomerRequest) GetEmail() string {
    if m != nil {
        return m.Email
    }
    return ""
}

func (m *CustomerRequest) GetPhone() string {
    if m != nil {
        return m.Phone
    }
    return ""
}

func (m *CustomerRequest) GetAddresses() []*CustomerRequest_Address {
    if m != nil {
        return m.Addresses
    }
    return nil
}

type CustomerRequest_Address struct {
    Street            string `protobuf:"bytes,1,opt,name=street" json:"street,omitempty"`
    City              string `protobuf:"bytes,2,opt,name=city" json:"city,omitempty"`
    State             string `protobuf:"bytes,3,opt,name=state" json:"state,omitempty"`
    Zip               string `protobuf:"bytes,4,opt,name=zip" json:"zip,omitempty"`
    IsShippingAddress bool   `protobuf:"varint,5,opt,name=isShippingAddress" json:"isShippingAddress,omitempty"`
}

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

func (m *CustomerRequest_Address) GetStreet() string {
    if m != nil {
        return m.Street
    }
    return ""
}

func (m *CustomerRequest_Address) GetCity() string {
    if m != nil {
        return m.City
    }
    return ""
}

func (m *CustomerRequest_Address) GetState() string {
    if m != nil {
        return m.State
    }
    return ""
}

func (m *CustomerRequest_Address) GetZip() string {
    if m != nil {
        return m.Zip
    }
    return ""
}

func (m *CustomerRequest_Address) GetIsShippingAddress() bool {
    if m != nil {
        return m.IsShippingAddress
    }
    return false
}

type CustomerResponse struct {
    Id      int32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
    Success bool  `protobuf:"varint,2,opt,name=success" json:"success,omitempty"`
}

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

func (m *CustomerResponse) GetId() int32 {
    if m != nil {
        return m.Id
    }
    return 0
}

func (m *CustomerResponse) GetSuccess() bool {
    if m != nil {
        return m.Success
    }
    return false
}

type CustomerFilter struct {
    Keyword string `protobuf:"bytes,1,opt,name=keyword" json:"keyword,omitempty"`
}

func (m *CustomerFilter) Reset()                    { *m = CustomerFilter{} }
func (m *CustomerFilter) String() string            { return proto.CompactTextString(m) }
func (*CustomerFilter) ProtoMessage()               {}
func (*CustomerFilter) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }

func (m *CustomerFilter) GetKeyword() string {
    if m != nil {
        return m.Keyword
    }
    return ""
}

func init() {
    proto.RegisterType((*CustomerRequest)(nil), "customer.CustomerRequest")
    proto.RegisterType((*CustomerRequest_Address)(nil), "customer.CustomerRequest.Address")
    proto.RegisterType((*CustomerResponse)(nil), "customer.CustomerResponse")
    proto.RegisterType((*CustomerFilter)(nil), "customer.CustomerFilter")
}

// 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

// Client API for Customer service

type CustomerClient interface {
    // Get all Customers with filter - A server-to-client streaming RPC.
    GetCustomers(ctx context.Context, in *CustomerFilter, opts ...grpc.CallOption) (Customer_GetCustomersClient, error)
    // Create a new Customer - A simple RPC
    CreateCustomer(ctx context.Context, in *CustomerRequest, opts ...grpc.CallOption) (*CustomerResponse, error)
}

type customerClient struct {
    cc *grpc.ClientConn
}

func NewCustomerClient(cc *grpc.ClientConn) CustomerClient {
    return &customerClient{cc}
}

func (c *customerClient) GetCustomers(ctx context.Context, in *CustomerFilter, opts ...grpc.CallOption) (Customer_GetCustomersClient, error) {
    stream, err := grpc.NewClientStream(ctx, &_Customer_serviceDesc.Streams[0], c.cc, "/customer.Customer/GetCustomers", opts...)
    if err != nil {
        return nil, err
    }
    x := &customerGetCustomersClient{stream}
    if err := x.ClientStream.SendMsg(in); err != nil {
        return nil, err
    }
    if err := x.ClientStream.CloseSend(); err != nil {
        return nil, err
    }
    return x, nil
}

type Customer_GetCustomersClient interface {
    Recv() (*CustomerRequest, error)
    grpc.ClientStream
}

type customerGetCustomersClient struct {
    grpc.ClientStream
}

func (x *customerGetCustomersClient) Recv() (*CustomerRequest, error) {
    m := new(CustomerRequest)
    if err := x.ClientStream.RecvMsg(m); err != nil {
        return nil, err
    }
    return m, nil
}

func (c *customerClient) CreateCustomer(ctx context.Context, in *CustomerRequest, opts ...grpc.CallOption) (*CustomerResponse, error) {
    out := new(CustomerResponse)
    err := grpc.Invoke(ctx, "/customer.Customer/CreateCustomer", in, out, c.cc, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// Server API for Customer service

type CustomerServer interface {
    // Get all Customers with filter - A server-to-client streaming RPC.
    GetCustomers(*CustomerFilter, Customer_GetCustomersServer) error
    // Create a new Customer - A simple RPC
    CreateCustomer(context.Context, *CustomerRequest) (*CustomerResponse, error)
}

func RegisterCustomerServer(s *grpc.Server, srv CustomerServer) {
    s.RegisterService(&_Customer_serviceDesc, srv)
}

func _Customer_GetCustomers_Handler(srv interface{}, stream grpc.ServerStream) error {
    m := new(CustomerFilter)
    if err := stream.RecvMsg(m); err != nil {
        return err
    }
    return srv.(CustomerServer).GetCustomers(m, &customerGetCustomersServer{stream})
}

type Customer_GetCustomersServer interface {
    Send(*CustomerRequest) error
    grpc.ServerStream
}

type customerGetCustomersServer struct {
    grpc.ServerStream
}

func (x *customerGetCustomersServer) Send(m *CustomerRequest) error {
    return x.ServerStream.SendMsg(m)
}

func _Customer_CreateCustomer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(CustomerRequest)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(CustomerServer).CreateCustomer(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: "/customer.Customer/CreateCustomer",
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(CustomerServer).CreateCustomer(ctx, req.(*CustomerRequest))
    }
    return interceptor(ctx, in, info, handler)
}

var _Customer_serviceDesc = grpc.ServiceDesc{
    ServiceName: "customer.Customer",
    HandlerType: (*CustomerServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "CreateCustomer",
            Handler:    _Customer_CreateCustomer_Handler,
        },
    },
    Streams: []grpc.StreamDesc{
        {
            StreamName:    "GetCustomers",
            Handler:       _Customer_GetCustomers_Handler,
            ServerStreams: true,
        },
    },
    Metadata: "customer.proto",
}

func init() { proto.RegisterFile("customer.proto", fileDescriptor0) }

var fileDescriptor0 = []byte{
    // 326 bytes of a gzipped FileDescriptorProto
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0xef, 0x4a, 0xc3, 0x30,
    0x10, 0xc0, 0x97, 0x6e, 0xdd, 0x9f, 0x53, 0xea, 0x0c, 0x22, 0xb1, 0x9f, 0x6a, 0x3f, 0x15, 0x91,
    0x21, 0xf3, 0xab, 0x20, 0x32, 0x70, 0xf8, 0xb5, 0x3e, 0x41, 0x6d, 0x0f, 0x17, 0xdc, 0xda, 0x9a,
    0xcb, 0x90, 0xf9, 0x0a, 0xbe, 0x83, 0xcf, 0xe0, 0x23, 0x4a, 0xd2, 0x66, 0x03, 0xe7, 0xbe, 0xdd,
    0xef, 0x72, 0x77, 0xf9, 0xe5, 0x08, 0x04, 0xf9, 0x9a, 0x74, 0xb5, 0x42, 0x35, 0xa9, 0x55, 0xa5,
    0x2b, 0x3e, 0x74, 0x1c, 0xff, 0x78, 0x70, 0x32, 0x6b, 0x21, 0xc5, 0xf7, 0x35, 0x92, 0xe6, 0x01,
    0x78, 0xb2, 0x10, 0x2c, 0x62, 0x89, 0x9f, 0x7a, 0xb2, 0xe0, 0x1c, 0x7a, 0x65, 0xb6, 0x42, 0xe1,
    0x45, 0x2c, 0x19, 0xa5, 0x36, 0xe6, 0x67, 0xe0, 0xe3, 0x2a, 0x93, 0x4b, 0xd1, 0xb5, 0xc9, 0x06,
    0x4c, 0xb6, 0x5e, 0x54, 0x25, 0x8a, 0x5e, 0x93, 0xb5, 0xc0, 0xef, 0x61, 0x94, 0x15, 0x85, 0x42,
    0x22, 0x24, 0xe1, 0x47, 0xdd, 0xe4, 0x68, 0x7a, 0x39, 0xd9, 0x1a, 0xfd, 0xb9, 0x7d, 0xf2, 0xd0,
    0x94, 0xa6, 0xbb, 0x9e, 0xf0, 0x8b, 0xc1, 0xa0, 0x4d, 0xf3, 0x73, 0xe8, 0x93, 0x56, 0x88, 0xda,
    0x0a, 0x8e, 0xd2, 0x96, 0x8c, 0x64, 0x2e, 0xf5, 0xc6, 0x49, 0x9a, 0xd8, 0xe8, 0x90, 0xce, 0x34,
    0x3a, 0x49, 0x0b, 0x7c, 0x0c, 0xdd, 0x4f, 0x59, 0xb7, 0x8a, 0x26, 0xe4, 0xd7, 0x70, 0x2a, 0xe9,
    0x79, 0x21, 0xeb, 0x5a, 0x96, 0xaf, 0xed, 0x45, 0xc2, 0x8f, 0x58, 0x32, 0x4c, 0xf7, 0x0f, 0xe2,
    0x3b, 0x18, 0xef, 0x9c, 0xa9, 0xae, 0x4a, 0xc2, 0xbd, 0x95, 0x09, 0x18, 0xd0, 0x3a, 0xcf, 0xcd,
    0x1c, 0xcf, 0xce, 0x71, 0x18, 0x5f, 0x41, 0xe0, 0xba, 0x1f, 0xe5, 0x52, 0xa3, 0x32, 0xb5, 0x6f,
    0xb8, 0xf9, 0xa8, 0x54, 0xd1, 0x3e, 0xc9, 0xe1, 0xf4, 0x9b, 0xc1, 0xd0, 0x15, 0xf3, 0x39, 0x1c,
    0xcf, 0x51, 0x3b, 0x24, 0x2e, 0xf6, 0x57, 0xd8, 0x0c, 0x0c, 0x2f, 0x0e, 0x2e, 0x37, 0xee, 0xdc,
    0x30, 0xfe, 0x04, 0xc1, 0x4c, 0x61, 0xa6, 0x71, 0x3b, 0xfa, 0x70, 0x43, 0x18, 0xfe, 0x77, 0xd4,
    0x3c, 0x3a, 0xee, 0xbc, 0xf4, 0xed, 0x77, 0xba, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xde, 0x91,
    0xd3, 0x62, 0x60, 0x02, 0x00, 0x00,
}

server/main.go

package main

import (
    "log"
    "net"
    "strings"

    "golang.org/x/net/context"
    "google.golang.org/grpc"

    pb "go_grpc_protobuf/customer"
)

const (
    port = ":50051"
)

// server is used to implement customer.CustomerServer.
type server struct {
    savedCustomers []*pb.CustomerRequest
}

// CreateCustomer creates a new Customer
func (s *server) CreateCustomer(ctx context.Context, in *pb.CustomerRequest) (*pb.CustomerResponse, error) {
    s.savedCustomers = append(s.savedCustomers, in)
    return &pb.CustomerResponse{Id: in.Id, Success: true}, nil
}

// GetCustomers returns all customers by given filter
func (s *server) GetCustomers(filter *pb.CustomerFilter, stream pb.Customer_GetCustomersServer) error {
    for _, customer := range s.savedCustomers {
        if filter.Keyword != "" {
            if !strings.Contains(customer.Name, filter.Keyword) {
                continue
            }
        }
        if err := stream.Send(customer); err != nil {
            return err
        }
    }
    return nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // Creates a new gRPC server
    s := grpc.NewServer()
    pb.RegisterCustomerServer(s, &server{})
    s.Serve(lis)
}

client/main.go

package main

import (
    "io"
    "log"

    "golang.org/x/net/context"
    "google.golang.org/grpc"

    pb "go_grpc_protobuf/customer"
)

const (
    address = "localhost:50051"
)

// createCustomer calls the RPC method CreateCustomer of CustomerServer
func createCustomer(client pb.CustomerClient, customer *pb.CustomerRequest) {
    resp, err := client.CreateCustomer(context.Background(), customer)
    if err != nil {
        log.Fatalf("Could not create Customer: %v", err)
    }
    if resp.Success {
        log.Printf("A new Customer has been added with id: %d", resp.Id)
    }
}

// getCustomers calls the RPC method GetCustomers of CustomerServer
func getCustomers(client pb.CustomerClient, filter *pb.CustomerFilter) {
    // calling the streaming API
    stream, err := client.GetCustomers(context.Background(), filter)
    if err != nil {
        log.Fatalf("Error on get customers: %v", err)
    }
    for {
        customer, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalf("%v.GetCustomers(_) = _, %v", client, err)
        }
        log.Printf("Customer: %v", customer)
    }
}
func main() {
    // Set up a connection to the gRPC server.
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    // Creates a new CustomerClient
    client := pb.NewCustomerClient(conn)

    customer := &pb.CustomerRequest{
        Id:    101,
        Name:  "Shiju Varghese",
        Email: "shiju@xyz.com",
        Phone: "732-757-2923",
        Addresses: []*pb.CustomerRequest_Address{
            &pb.CustomerRequest_Address{
                Street:            "1 Mission Street",
                City:              "San Francisco",
                State:             "CA",
                Zip:               "94105",
                IsShippingAddress: false,
            },
            &pb.CustomerRequest_Address{
                Street:            "Greenfield",
                City:              "Kochi",
                State:             "KL",
                Zip:               "68356",
                IsShippingAddress: true,
            },
        },
    }

    // Create a new customer
    createCustomer(client, customer)

    customer = &pb.CustomerRequest{
        Id:    102,
        Name:  "Irene Rose",
        Email: "irene@xyz.com",
        Phone: "732-757-2924",
        Addresses: []*pb.CustomerRequest_Address{
            &pb.CustomerRequest_Address{
                Street:            "1 Mission Street",
                City:              "San Francisco",
                State:             "CA",
                Zip:               "94105",
                IsShippingAddress: true,
            },
        },
    }

    // Create a new customer
    createCustomer(client, customer)
    // Filter with an empty Keyword
    filter := &pb.CustomerFilter{Keyword: ""}
    getCustomers(client, filter)
}

运行server,运行client,输出:

2017/12/07 11:55:59 A new Customer has been added with id: 101
2017/12/07 11:55:59 A new Customer has been added with id: 102
2017/12/07 11:55:59 Customer: id:101 name:"Shiju Varghese" email:"shiju@xyz.com" phone:"732-757-2923" addresses:"1 Mission Street" city:"San Francisco" state:"CA" zip:"94105" > addresses:"Greenfield" city:"Kochi" state:"KL" zip:"68356" isShippingAddress:true >
2017/12/07 11:55:59 Customer: id:102 name:"Irene Rose" email:"irene@xyz.com" phone:"732-757-2924" addresses:"1 Mission Street" city:"San Francisco" state:"CA" zip:"94105" isShippingAddress:true >

这里写图片描述


推荐阅读
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • 微信官方授权及获取OpenId的方法,服务器通过SpringBoot实现
    主要步骤:前端获取到code(wx.login),传入服务器服务器通过参数AppID和AppSecret访问官方接口,获取到OpenId ... [详细]
  • 本文介绍了使用C++Builder实现获取USB优盘序列号的方法,包括相关的代码和说明。通过该方法,可以获取指定盘符的USB优盘序列号,并将其存放在缓冲中。该方法可以在Windows系统中有效地获取USB优盘序列号,并且适用于C++Builder开发环境。 ... [详细]
  • tcpdump 4.5.1 crash 深入分析
    tcpdump 4.5.1 crash 深入分析 ... [详细]
  • SOA架构理解理解SOA架构,了解ESB概念,明白SOA与微服务的区别和联系,了解SOA与热门技术的结合与应用。1、面向服务的架构SOASOA(ServiceOrien ... [详细]
  • 微服务下的几个难点问题及常见的解决方案
    原文链接:https:cloud.tencent.comdevelopernews1362051背景介绍1.1幂等性定义数学定义在数学里,幂等有 ... [详细]
  • 微服务之总体架构篇
    一、单体架构存在的问题缺点:1、难以维护:当单体应用业务不断迭代后代码量非常臃肿,模整个项目非常复杂,每次更改代码都可能带来新的bug;2、部署项目麻烦:庞大之后项目部署效率 ... [详细]
  • 思考题|传统_一本教你如何编写高质量代码的图书:《设计模式之美》
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了一本教你如何编写高质量代码的图书:《设计模式之美》相关的知识,希望对你有一定的参考价值。软件工程师都很重视代码质量&#x ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 使用freemaker生成Java代码的步骤及示例代码
    本文介绍了使用freemaker这个jar包生成Java代码的步骤,通过提前编辑好的模板,可以避免写重复代码。首先需要在springboot的pom.xml文件中加入freemaker的依赖包。然后编写模板,定义要生成的Java类的属性和方法。最后编写生成代码的类,通过加载模板文件和数据模型,生成Java代码文件。本文提供了示例代码,并展示了文件目录结构。 ... [详细]
  • 阿里首席架构师科普RPC框架
    RPC概念及分类RPC全称为RemoteProcedureCall,翻译过来为“远程过程调用”。目前,主流的平台中都支持各种远程调用技术,以满足分布式系统架构中不同的系统之间的远程 ... [详细]
author-avatar
手机用户2602937685
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有