概念

rpc 远程过程调用(Remote Procedure Call),简单理解为一台服务器调用另一台服务器的函数,Go语言中的RPC默认使用的是gob协议(其他语言不支持), 数据传输方式有TCP和HTTP。

TCP-GOB

服务端

package main

import (
    "fmt"
    "net"
    "net/rpc"
)

type UserService struct {
}

func (s *UserService) Hello(request string, reply *string) error {
    *reply = fmt.Sprintf("hello %s", request)
    return nil
}

func main() {
    // 监听端口
    listen, err := net.Listen("tcp", "0.0.0.0:9100")
    if err != nil {
        panic(any(err))
    }
    // 注册rpc 服务
    err = rpc.RegisterName("UserService", &UserService{})
    if err != nil {
        panic(any(err))
    }
    // 接受客户端链接请求
    conn, err := listen.Accept()
    if err != nil {
        panic(any(err))
    }
    // 启动服务
    rpc.ServeConn(conn)
}

客户端

package main

import (
    "fmt"
    "net/rpc"
)

func main() {
    // 链接rpc 服务
    client, err := rpc.Dial("tcp", "127.0.0.1:9100")
    if err != nil {
        panic(any(err))
    }
    // 定义请求参数
    var reply string
    param := "Gyi"
    serverMethod := "UserService.Hello"
    // 服务调用
    err = client.Call(serverMethod, param, &reply)
    if err != nil {
        panic(any(err))
    }
    fmt.Printf("receive message: %s\n", reply)
}

测试

启动服务

go run server/server.go

调用服务

# go run client/client.go
receive message: hello Gyi

优化

测试中发现客户端调用服务后,服务端会结束退出.查看 ServerConn 说明,ServeConn 是阻塞为链接提供服务,知道客户端挂断后结束. 现将服务改成可无限调用只需要为 Accept 和 ServeConn 加上死循环即可。服务端代码更改如下:
package main

import (
    "fmt"
    "net"
    "net/rpc"
)

type UserService struct {
}

func (s *UserService) Hello(request string, reply *string) error {
    *reply = fmt.Sprintf("hello %s", request)
    return nil
}

func main() {
    // 监听端口
    listen, err := net.Listen("tcp", "0.0.0.0:9100")
    if err != nil {
        panic(any(err))
    }
    // 注册rpc 服务
    err = rpc.RegisterName("UserService", &UserService{})
    if err != nil {
        panic(any(err))
    }
    for {
        // 接受客户端链接请求
        conn, err := listen.Accept()
        if err != nil {
            panic(any(err))
        }
        // 启动服务
        rpc.ServeConn(conn)
    }
}

TCP-JSON

因go的rpc默认使用的是gob编码协议,其他语言无法调用,所以要用其他语言调用需要修改为通用的编码协议

服务端

package main

import (
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type UserService struct {
}

func (s *UserService) Hello(request string, reply *string) error {
    *reply = fmt.Sprintf("hello %s", request)
    return nil
}

func main() {
    // 监听端口
    listen, err := net.Listen("tcp", "0.0.0.0:9100")
    if err != nil {
        panic(any(err))
    }
    // 注册rpc 服务
    err = rpc.RegisterName("UserService", &UserService{})
    if err != nil {
        panic(any(err))
    }
    for {
        // 接受客户端链接请求
        conn, err := listen.Accept()
        if err != nil {
            panic(any(err))
        }
        // 启动服务
        jsonrpc.ServeConn(conn)
    }
}

客户端

Go

package main

import (
    "fmt"
    "net/rpc/jsonrpc"
)

func main() {
    // 链接json-rpc 服务
    client, err := jsonrpc.Dial("tcp", "127.0.0.1:9100")
    if err != nil {
        panic(any(err))
    }
    // 定义请求参数
    var reply string
    param := "Gyi"
    serverMethod := "UserService.Hello"
    // 服务调用
    err = client.Call(serverMethod, param, &reply)
    if err != nil {
        panic(any(err))
    }
    fmt.Printf("receive message: %s\n", reply)
}

PHP

<?php

$sendMsg = json_encode([
    "method" => "UserService.Hello",
    "params" => ["Gyi", "Syn"],
    "id" => mt_rand(1000, 9999),
]);
$msgLen = strlen($sendMsg);


$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_block($socket);
socket_connect($socket, "127.0.0.1", 9100);

$msg = socket_write($socket, $sendMsg, $msgLen);
// 接受返回的数据
$recvData = socket_read($socket, 1024);

echo "发送数据:" . $sendMsg . PHP_EOL;
echo "接受数据:" .  $recvData . PHP_EOL;

socket_close($socket);

优化

  1. 远程服务调用优化成像本地服务调用一样简单
  2. 抽离服务,不会因服务改名而同时修改服务端和客户端

目录结构

├─client                       客户端目录
│  ├─client.go          go客户端文件
│  ├─client.php         PHP客户端文件
│  ├─proxy                    代理目录
│    └─ proxy.go        代理文件
│  
├─helper                帮助函数目录
│  └─help.go            公共配置文件
│  
├─server                服务端目录
│  ├─server.go          rpc服务文件
│  ├─proxy                    代理目录
│    └─ proxy.go        服务代理文件

公共函数

package helper

const (
    UserServerNetwork = "tcp"
    UserServerAddr    = "0.0.0.0:9100"
    UserServerName    = "helper/UserServer" // 用户服务名称
)

服务端

代理

package proxy

import (
    "grpc-demo/rpc-upgrade-test/helper"
    "net/rpc"
)

type UserServicer interface {
    Hello(string, *string) error
}

// RegisterUserServer 注册用户服务
// 只要结构实现了 Hello 方法就可以调用此函数.
func RegisterUserServer(s UserServicer) error {
    return rpc.RegisterName(helper.UserServerName, s)
}

RPC-Server

package main

import (
    "fmt"
    "grpc-demo/rpc-upgrade-test/helper"
    "grpc-demo/rpc-upgrade-test/server/proxy"
    "net"
    "net/rpc/jsonrpc"
)

type UserService struct {
}

func (s *UserService) Hello(request string, reply *string) error {
    *reply = fmt.Sprintf("hello %s", request)
    return nil
}

func main() {
    // 监听端口
    listen, err := net.Listen(helper.UserServerNetwork, helper.UserServerAddr)
    if err != nil {
        panic(any(err))
    }
    // 注册rpc 服务
    err = proxy.RegisterUserServer(&UserService{})
    if err != nil {
        panic(any(err))
    }
    for {
        // 接受客户端链接请求
        conn, err := listen.Accept()
        if err != nil {
            panic(any(err))
        }
        // 启动服务
        jsonrpc.ServeConn(conn)
    }
}

客户端

代理

package proxy

import (
    "grpc-demo/rpc-upgrade-test/helper"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type Service struct {
    serverName string
    client     *rpc.Client
}

// NewUserServerClient 相当于实例化__construct操作
func NewUserServerClient() *Service {
    client, err := jsonrpc.Dial(helper.UserServerNetwork, helper.UserServerAddr)
    if err != nil {
        panic(any(err))
    }
    return &Service{
        serverName: helper.UserServerName,
        client:     client,
    }
}

func (s *Service) Hello(param string, reply *string) error {
    serverMethod := s.serverName + ".Hello"
    err := s.client.Call(serverMethod, param, reply)
    if err != nil {
        panic(any(err))
    }
    return nil
}

RPC-Client

package main

import (
    "fmt"
    "grpc-demo/rpc-upgrade-test/client/proxy"
)

func main() {
    var reply string
    param := "Ynn"
    userClientServer := proxy.NewUserServerClient()
    _ = userClientServer.Hello(param, &reply)
    fmt.Printf("receive message: %s\n", reply)
}

测试

启动服务

go run server/server.go

调用服务

# go run client/client.go
receive message: hello Ynn
Last modification:June 10th, 2022 at 09:14 pm