preface

Dubbo/Dubbox, Google gRPC, RPCX, Spring Boot/Spring Cloud are so many RPC frameworks that we need to know how they work

We use LightRPC github.com/dollarkillerx/light, for example Write a belongs to own the RPC

What was the end result

Github.com/dollarkille…

payload:

/ / request body
strcut Request {
    Msg string
}
/ / return to the body
struct Response {
    Msg string
}
Copy the code

server:


// Define the basic service
struct HelloWorldServer {}

func (h *HelloWorldServer) HelloWorld(ctx *light.Context, request *Request, response *Response) error {

    response.Msg = fmt.Sprintf("request: %s ",request.Msg)
    return nil
}

// Start the service
func main(a) { 
    ser := server.NewServer() 
    err := ser.RegisterName(&HelloWorldServer{}, "HelloWorldServer")  // Registration service
    iferr ! =nil {
        log.Fatalln(err) 
    } 
    
    // Listen to the service
    if err := ser.Run(server.UseTCP("0.0.0.0:8074")); err ! =nil {
        log.Fatalln(err) } 
    }
Copy the code

client:

	client := client.NewClient(discovery.NewSimplePeerToPeer("127.0.0.1:8074", transport.TCP))
	connect, err := client.NewConnect("HelloWorldServer") // Establish a connection to a specific service
	iferr ! =nil {
		log.Fatalln(err)
		return
	}

	req := Request{
		Msg: "hello",
	}
	resp := Response{}
	err = connect.Call(light.DefaultCtx(), "HelloWorld", &req, &resp) // Call a service
	iferr ! =nil {
		log.Fatalln(err)
	}
        
        fmt.Println(resp)
Copy the code

This is the basic service we want to implement, we can add service discovery and registration fuse monitoring on top of this service…

1. Implement internal service registration

Let’s start with the basic requirements on the server side:

  • 1. Multiple services can be registered on a server
  • 2. middleware

Let’s start by defining a service manager to manage multiple services

type MiddlewareFunc func(ctx *light.Context, request interface{}, response interface{}) error

// Define the service manager
type Server struct {
	serviceMap map[string]*service  // The service is registered using a map
	options    *Options             // Related configuration

	beforeMiddleware     []MiddlewareFunc       // Premiddleware
	afterMiddleware      []MiddlewareFunc       // Middleware is used globally
	beforeMiddlewarePath map[string][]MiddlewareFunc   // Premiddleware for specific routes
	afterMiddlewarePath  map[string][]MiddlewareFunc   // Put middleware behind specific routes
}


// Define a single service
type service struct {
	name       string                 // Server name Indicates the service name
	refVal     reflect.Value          // server reflect value
	refType    reflect.Type           // server reflect type
	methodType map[string]*methodType // Server method Specifies the server method
}
Copy the code

We’ve just defined the service and now we’re going to complete the service initialization code

func NewServer(a) *Server {
	return &Server{
		serviceMap: map[string]*service{},
		options:    defaultOptions(),

		beforeMiddleware:     []MiddlewareFunc{},
		afterMiddleware:      []MiddlewareFunc{},
		beforeMiddlewarePath: map[string][]MiddlewareFunc{},
		afterMiddlewarePath:  map[string][]MiddlewareFunc{},
	}
}
Copy the code

Now write the service registry, which is the focus of this chapter!!

We will define how two services are registered

  1. To Register the service directly with Register(), the service name is set to the name of the current structure
  2. RegisterName() is used to register a service, which can be set by passing in the service name

func (s *Server) Register(server interface{}) error {
	return s.register(server, "".false)}func (s *Server) RegisterName(server interface{}, serverName string) error {
	return s.register(server, serverName, true)}// The specific service registration method
func (s *Server) register(server interface{}, serverName string, useName bool) error {
	ser, err := newService(server, serverName, useName)  // Generate a service
	iferr ! =nil {
		return err
	}

	s.serviceMap[ser.name] = ser  // Put it in serviceMap
	return nil
}
Copy the code

Constructing specific services

func newService(server interface{}, serverName string, useName bool) (*service, error) {
	ser := &service{
		refVal:  reflect.ValueOf(server),
		refType: reflect.TypeOf(server),
	}

        // Get the name of the service
	sName := reflect.Indirect(ser.refVal).Type().Name()
	if! utils.IsPublic(sName) {// IsPublic determines whether it IsPublic
		return nil, pkg.ErrNonPublic
	}

	if useName {
		if serverName == "" {
			return nil, errors.New("Server Name is null")
		}

		sName = serverName
	}

	ser.name = sName
        
        // constructionMethods Obtain the compliance methods of the current structure for registration
	methods, err := constructionMethods(ser.refType)
	iferr ! =nil {
		return nil, err
	}
	ser.methodType = methods

	for _, v := range methods {
		log.Println("Registry Service: ", ser.name, " method: ", v.method.Name)
	}

	return ser, nil
}


// constructionMethods Get specific method
func constructionMethods(typ reflect.Type) (map[string]*methodType, error) {
	methods := make(map[string]*methodType)
	for idx := 0; idx < typ.NumMethod(); idx++ {  // We iterate through the methods of the current struct to find the methods that match and register them
		method := typ.Method(idx)
		mType := method.Type
		mName := method.Name

		if! utils.IsPublic(mName) {return nil, pkg.ErrNonPublic
		}

		// The default is 4
		ifmType.NumIn() ! =4 { // func(*server.MethodTest, *light.Context, *server.MethodTestReq, *server.MethodTestResp) error
			continue
		}

		// Check whether the first argument is CTX
		ctxType := mType.In(1)
		if! (ctxType.Elem() == typeOfContext) {continue
		}

		// Check the request parameter
		requestType := mType.In(2)
		ifrequestType.Kind() ! = reflect.Ptr {continue
		}
                
                // Check whether all parameters are public
		if! utils.IsPublicOrBuiltinType(requestType) {continue
		}

		// Check the response parameter
		responseType := mType.In(3)
		ifresponseType.Kind() ! = reflect.Ptr {continue
		}

		if! utils.IsPublicOrBuiltinType(responseType) {continue
		}

		// Verify the returned parameters
		ifmType.NumOut() ! =1 {
			continue
		}

		returnType := mType.Out(0)
		ifreturnType ! = typeOfError {continue
		}

		methods[mName] = &methodType{
			method:       method,
			RequestType:  requestType,
			ResponseType: responseType,
		}
	}

	if len(methods) == 0 {
		return nil, pkg.ErrNoAvailable
	}

	return methods, nil
}
Copy the code

That completes the basic service registration

Column:Juejin. Cn/column / 6986…