Go microservice framework-2 Go language RPC programming practice

Posted by oboedrew on Wed, 02 Feb 2022 23:31:06 +0100

RPC programming with Go language

In the last lesson, we introduced the knowledge of RPC, explained the principle of RPC, and explained the internal execution process of RPC in a graphical way. In this lesson, we will continue to learn about RPC.

RPC official library

The official supported rpc package is provided in the pkg description of the official Go language website. The specific links are as follows: https://golang.org/pkg/net/rpc/ . The complete package name of the official rpc package is: net/rpc. According to the official explanation, rpc package mainly provides the function of accessing an object method through the network.

In this lesson, we will learn how to use the RPC package officially provided by go language to realize RPC call coding.

net/rpc library to realize RPC calling programming

As we mentioned earlier, rpc calls have two participants: client and server.

The first is to provide the party exposed by the method - the server.

1, Service definition and exposure

In the process of programming, the server needs to register the structure object, and then expose it to the caller through the method to which the object belongs, so as to provide services. This method is called output method, which can be called remotely. Of course, when defining output methods, methods that can be called remotely need to follow certain rules. Let's explain through the code:

func (t *T) MethodName(request T1,response *T2) error

The above code is the official definition standard of service methods exposed to the public given by go language, which contains several main rules, namely:

  • 1. Exposed methods have and can only have two parameters, which can only be output type or built-in type, one of the two types.
  • 2. The second argument to the method must be a pointer type.
  • 3. The return type of the method is error.
  • 4. The type of the method is exportable.
  • 5. The method itself is also exportable.

Let's give an example: suppose we have a requirement to give a float type variable as the radius of the circle. It is required to return the corresponding circular area through RPC call. The specific programming ideas are as follows:

type MathUtil struct{
}
//This method is exposed to the outside: it provides the service of calculating the circular area
func (mu *MathUtil) CalculateCircleArea(req float32, resp *float32) error {
	*resp = math.Pi * req * req //Area of circle s = π * r * r
	return nil //Return type
}

In the above case, we can see:

  • 1. The Calculate method is a service method provided by the service object MathUtil. This method is used to receive the incoming circular radius data, Calculate the circular area and return.
  • 2. The first parameter req represents the parameters provided by the caller (client).
  • 3. The second parameter resp represents the calculation result to be returned to the caller, which must be of pointer type.
  • 4. Normally, the return value of the method is error and nil. If an exception or special case is encountered, error will be returned to the caller as a string, and the resp parameter will no longer be returned to the caller.

So far, the function definition of the server has been realized. The next step is to make the service code effective. You need to register the service and start request processing.

2, Registration service and listening request

net/rpc package provides us with registration services and a series of methods for processing requests. Combined with this case, we realize the registration and processing logic, as shown below:

//1. Initialize pointer data type
mathUtil := new(MathUtil) //Initialize pointer data type

//2. Call the function of net/rpc package to register the service object
err := rpc.Register(mathUtil)
if err != nil {
	panic(err.Error())
}

//3. This function registers the services provided in mathUtil to the HTTP protocol, so that the caller can use HTTP to transfer data
rpc.HandleHTTP()

//4. Listen on a specific port
listen, err := net.Listen("tcp", ":8081")
if err != nil {
	panic(err.Error())
}
go http.Serve(listen, nil)

After service registration and listening processing, the server implementation in the RPC call process has been completed. The next step is to implement the client request code.

3, Client call

The server is waiting for connection through the port listening mode of HTTP. Therefore, the client needs to connect through HTTP and connect with the server first.

  • Client connection server

    client, err := rpc.DialHTTP("tcp", "localhost:8081")
    if err != nil {
    	panic(err.Error())
    }
    
  • remote method invocation
    After the client successfully connects to the server, the method of the server can be called through method call. The specific calling methods are as follows:

    var req float32 //Request value
    req = 3
    
    var resp *float32 //Return value
    err = client.Call("MathUtil.CalculateCircleArea", req, &resp)
    if err != nil {
    panic(err.Error())
    }
    fmt.Println(*resp)
    

    The core of the above calling method is client Call method, which has three parameters. The first parameter represents the method name of the remote service to be called, the second parameter is the parameter to be passed in during the call, and the third parameter is the return value to be received during the call.
    The above Call method calls are implemented in a synchronous way. In addition, there is an asynchronous way to Call. The asynchronous calling code is implemented as follows:

    var respSync *float32
    //Asynchronous call mode
    syncCall := client.Go("MathUtil.CalculateCircleArea", req, &respSync, nil)
    replayDone := <-syncCall.Done
    fmt.Println(replayDone)
    fmt.Println(*respSync)
    
Multi parameter request call parameter passing

The above content demonstrates how to implement RPC calls under single parameters and requests under multiple parameters. We use another case to demonstrate.

Suppose you need to realize another requirement now: calculate the addition of two numbers through RPC call and return the calculation result. At this point, you need to pass two parameters. The specific implementation is as follows:

Define parameters in a new structure and store them in param package:

type AddParma struct {
	Args1 float32 //First parameter
	Args2 float32 //Second parameter
}

On the server In the go file, the function of adding two numbers and the logic of service registration are realized:

func (mu *MathUtil) Add(param param.AddParma, resp *float32) error {
	*resp = param.Args1 + param.Args2 //Realize the function of adding two numbers
        return nil
}
mathUtil := new(MathUtil)

err := rpc.RegisterName("MathUtil", mathUtil)
if err != nil {
	panic(err.Error())
}

rpc.HandleHTTP()

listen, err := net.Listen("tcp", ":8082")
http.Serve(listen, nil)

In this case, we use the new registration method RPC Registername implements the registration and invocation of services.

So far, we have completed the most basic use of net/rpc package.