本项目演示了如何使用JSON-RPC协议实现Go语言和Python之间的跨语言通信。项目包含两种场景的完整实现,展示了双向的RPC调用能力。

项目概述

JSON-RPC是一种轻量级的远程过程调用协议,使用JSON作为数据交换格式。相比gRPC,JSON-RPC更加简单易用,特别适合跨语言通信场景。

本demo项目实现了一个简单的算术运算服务,包含乘法(Multiply)和加法(Plus)两个方法,展示了以下两种场景:

  1. 场景一:Go语言作为服务器,Python作为客户端
  2. 场景二:Python作为服务器,Go语言作为客户端

场景一:Go服务器 + Python客户端

Go服务器实现

Go服务器使用标准库中的net/rpcnet/rpc/jsonrpc包来实现JSON-RPC服务。

文件:json_rpc_server.go

package main

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

type Arith int

type Args struct {
	A, B int
}

// 乘法
func (t *Arith) Mulitply(args *Args, reply *int) error {
	*reply = args.A * args.B
	return nil
}

func (t *Arith) Plus(args *Args, reply *int) error {
	*reply = args.A + args.B
	return nil
}

func main() {
	addr := ":1234"
	server := rpc.NewServer()
	server.Register(new(Arith))

	l, e := net.Listen("tcp", addr)
	if e != nil {
		log.Fatalln("listen error:", e)
	} else {
		log.Println("rpc listening ", addr)
	}

	defer l.Close()

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Print("rpc.Serve: accept:", err.Error())
			return
		}
		go server.ServeCodec(jsonrpc.NewServerCodec(conn))
	}
}

Python客户端实现

Python客户端使用socket和json标准库实现JSON-RPC客户端功能。

文件:json_rpc_client.py

import json
import socket
import itertools

class RPCClient(object):

    def __init__(self, addr, codec=json):
        self._socket = socket.create_connection(addr)
        self._id_iter = itertools.count()
        self._codec = codec

    def _message(self, name, *params):
        return dict(id=next(self._id_iter),
                    params=list(params),
                    method=name)

    def call(self, name, *params):
        req = self._message(name, *params)
        id = req.get('id')
        
        """
        Golang Rpc 返回的Json格式
        type serverResponse struct {
        Id     *json.RawMessage `json:"id"`
        Result interface{}      `json:"result"`
        Error  interface{}      `json:"error"`
        }
        """

        mesg = self._codec.dumps(req)
        mesg = mesg.encode(encoding='utf-8')
        self._socket.sendall(mesg)

        # This will actually have to loop if resp is bigger
        resp = self._socket.recv(4096)
        resp = resp.decode(encoding='utf-8')
        resp = self._codec.loads(resp)

        if resp.get('id') != id:
            raise Exception("expected id=%s, received id=%s: %s"
                            %(id, resp.get('id'), resp.get('error')))

        if resp.get('error') is not None:
            raise Exception(resp.get('error'))

        return resp.get('result')

    def close(self):
        self._socket.close()


if __name__ == '__main__':
    rpc = RPCClient(("127.0.0.1", 1234))
    args = {'A':203, 'B':3}
    print(rpc.call("Arith.Mulitply",args))
    print(rpc.call("Arith.Plus",args))

运行场景一

  1. 启动Go服务器:
go run json_rpc_server.go
  1. 运行Python客户端:
python json_rpc_client.py

输出结果:

609
206

场景二:Python服务器 + Go客户端

Python服务器实现

Python服务器使用多线程处理并发连接,实现了完整的JSON-RPC协议。

文件:py_server_go_client/json_rpc_server.py

import json
import socket
import threading

# Define the arithmetic service
class ArithService:
    def Multiply(self, args):
        return args['A'] * args['B']
    
    def Plus(self, args):
        return args['A'] + args['B']

# JSON-RPC Server
class JSONRPCServer:
    def __init__(self, host='0.0.0.0', port=1234):
        self.host = host
        self.port = port
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind((host, port))
        self.socket.listen(5)
        self.running = False
        self.services = {}
        
    def register_service(self, name, service):
        self.services[name] = service
        
    def handle_client(self, client_socket):
        while True:
            try:
                # Read request
                data = client_socket.recv(4096)
                if not data:
                    break
                    
                # Parse request
                request = json.loads(data.decode('utf-8'))
                method_name = request.get('method')
                params = request.get('params', [])
                request_id = request.get('id')
                
                # Process request
                result = None
                error = None
                
                try:
                    if '.' in method_name:
                        service_name, method = method_name.split('.', 1)
                        service = self.services.get(service_name)
                        if service and hasattr(service, method):
                            method_func = getattr(service, method)
                            result = method_func(params[0])
                        else:
                            error = f"Method not found: {method_name}"
                    else:
                        error = f"Invalid method format: {method_name}"
                except Exception as e:
                    error = str(e)
                
                # Send response
                response = {
                    'id': request_id,
                    'result': result,
                    'error': error
                }
                
                client_socket.sendall(json.dumps(response).encode('utf-8'))
            except Exception as e:
                print(f"Error handling client request: {e}")
                break
        
        client_socket.close()
    
    def start(self):
        self.running = True
        print(f"JSON-RPC server listening on {self.host}:{self.port}")
        
        try:
            while self.running:
                client_socket, _ = self.socket.accept()
                client_thread = threading.Thread(target=self.handle_client, args=(client_socket,))
                client_thread.daemon = True
                client_thread.start()
        except KeyboardInterrupt:
            self.stop()
    
    def stop(self):
        self.running = False
        self.socket.close()
        print("Server stopped")

if __name__ == '__main__':
    # Create server
    server = JSONRPCServer()
    
    # Register services
    server.register_service('Arith', ArithService())
    
    # Start server
    try:
        server.start()
    except KeyboardInterrupt:
        server.stop()

Go客户端实现

Go客户端使用标准库的net/rpc/jsonrpc包连接Python服务器。

文件:py_server_go_client/json_rpc_client.go

package main

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

// Args represents the arguments for arithmetic operations
type Args struct {
	A int `json:"A"`
	B int `json:"B"`
}

func main() {
	// Connect to the Python JSON-RPC server
	client, err := jsonrpc.Dial("tcp", "127.0.0.1:1234")
	if err != nil {
		log.Fatal("dialing:", err)
	}
	defer client.Close()

	// Prepare arguments
	args := &Args{A: 203, B: 3}
	
	// Call Multiply method
	var multiplyResult int
	err = client.Call("Arith.Multiply", args, &multiplyResult)
	if err != nil {
		log.Fatal("Multiply error:", err)
	}
	fmt.Printf("Multiply: %d * %d = %d\n", args.A, args.B, multiplyResult)

	// Call Plus method
	var plusResult int
	err = client.Call("Arith.Plus", args, &plusResult)
	if err != nil {
		log.Fatal("Plus error:", err)
	}
	fmt.Printf("Plus: %d + %d = %d\n", args.A, args.B, plusResult)
}

运行场景二

  1. 启动Python服务器:
cd py_server_go_client
python json_rpc_server.py
  1. 运行Go客户端:
cd py_server_go_client
go run json_rpc_client.go

输出结果:

Multiply: 203 * 3 = 609
Plus: 203 + 3 = 206

技术要点

JSON-RPC协议格式

请求格式:

{
    "id": 1,
    "method": "Arith.Multiply",
    "params": [{"A": 203, "B": 3}]
}

响应格式:

{
    "id": 1,
    "result": 609,
    "error": null
}

实现特点

  1. Go服务器:使用官方RPC库,支持方法注册和自动路由
  2. Python服务器:手动实现JSON-RPC协议,支持服务注册和多线程处理
  3. 跨语言兼容:两种语言的客户端和服务器可以互相通信
  4. 错误处理:包含完整的错误处理机制
  5. 并发支持:服务器支持多客户端并发访问

总结

本项目展示了JSON-RPC在跨语言通信中的实用性。相比gRPC,JSON-RPC具有以下优势:

  • 简单易用:协议简单,易于理解和实现
  • 跨语言友好:基于JSON,几乎所有语言都有很好的支持
  • 调试方便:可读的JSON格式便于调试
  • 轻量级:协议开销小,适合简单的RPC场景

通过本demo,可以快速上手跨语言RPC开发,为分布式系统的不同语言组件提供通信基础。