概念
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);
优化
- 远程服务调用优化成像本地服务调用一样简单
- 抽离服务,不会因服务改名而同时修改服务端和客户端
目录结构
├─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