英文:
Ruby Socket Client hanging when calling Go RPC TCP Service
问题
我有一个用Go编写的工作中的RPC TCP服务,但是当使用Ruby连接到该服务时,它会挂起,因为似乎没有通过打开的套接字连接发送数据回来。
远程RPC函数:
package remote
import "fmt"
// Compose是我们的RPC函数的返回类型
type Compose string
// Details是我们暴露的RPC函数
func (c *Compose) Details(arg string, reply *string) error {
fmt.Printf("接收到的参数: %+v\n", arg)
*c = "一些值"
*reply = "Blah!"
return nil
}
暴露的远程RPC端点:
package remote
import (
"fmt"
"net"
"net/rpc"
)
// Endpoint通过TCP暴露我们的RPC服务
func Endpoint() {
compose := new(Compose)
rpc.Register(compose)
listener, err := net.Listen("tcp", ":8080")
// 省略错误处理
for {
conn, err := listener.Accept()
// 省略错误处理
go rpc.ServeConn(conn)
}
}
通过TCP连接到远程RPC函数的客户端(使用Golang,所以至少在这方面我知道它是工作的):
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
client, err := rpc.Dial("tcp", "localhost:8080")
if err != nil {
log.Fatal("拨号错误:", err)
}
var reply string
e := client.Call("Compose.Details", "我的字符串", &reply)
if e != nil {
log.Fatalf("出现错误:%v", e.Error())
}
fmt.Printf("'reply'指针的值已更改为:%s", reply)
}
这是我尝试使用的Ruby代码:
require "socket"
require "json"
socket = TCPSocket.new "localhost", "8080"
b = {
method: "Compose.Details",
params: "foobar"
}
socket.write(JSON.dump(b))
resp = JSON.load(socket.readline)
p resp
我认为它不起作用的原因是Go RPC要求在暴露的RPC函数的第二个参数中提供一个指针,但是我不确定在Ruby等其他编程语言中如何工作?
我还尝试使用类似于https://github.com/chriskite/jimson的库:
require "jimson"
client = Jimson::Client.new("localhost:8080")
result = client.Details("foobar")
但是我只得到以下结果:
RestClient::ServerBrokeConnection: 服务器断开连接
更新
正如JimB在评论中指出的那样,我需要使用JSON RPC库。事情还不太顺利,你会看到...
这是更新后的Ruby客户端:
require "socket"
require "json"
socket = TCPSocket.new "localhost", "8080"
b = {
method: "Compose.Details",
params: [{ :args => { :foo => "Foo!", :bar => "Bar!" }, :id => "0" }]
}
socket.write(JSON.dump(b))
p JSON.load(socket.readline)
接下来是更新后的Go客户端:
package remote
import "fmt"
// Args围绕客户端提供的参数进行结构化
type Args struct {
foo string
bar string
}
// Compose是我们的RPC函数的返回类型
type Compose string
// Details是我们暴露的RPC函数
func (c *Compose) Details(args *Args, reply *string) error {
fmt.Printf("接收到的参数: %+v\n", args)
*c = "一些值"
*reply = "Blah!"
return nil
}
当我运行这个时,我的Ruby客户端看到以下内容:
{"id"=>nil, "result"=>"Blah!", "error"=>nil}
所以我得到了一个结果,但是如果我的实际RPC代码尝试使用提供的参数以任何方式生成结果,那么这将是一个错误的示例,因为我从Go RPC函数得到的标准输出是一组空的参数值?
接收到的参数: &{foo: bar:}
我不确定为什么RPC函数无法获取我提供给它的参数?
另外,“id”键:为什么我会从RPC函数那里得到一个?我如何给它一个有意义的值?
我尝试在我的Ruby客户端中设置一个带有值的“id”键,但是它没有影响到Go RPC的响应。
英文:
I have a working RPC TCP service written in Go, but when using Ruby to connect to the service it hangs because no data appears to be sent back through the open socket connection.
Remote RPC Function:
package remote
import "fmt"
// Compose is our RPC functions return type
type Compose string
// Details is our exposed RPC function
func (c *Compose) Details(arg string, reply *string) error {
fmt.Printf("Arg received: %+v\n", arg)
*c = "some value"
*reply = "Blah!"
return nil
}
Remote RPC Endpoint Exposed:
package remote
import (
"fmt"
"net"
"net/rpc"
)
// Endpoint exposes our RPC over TCP service
func Endpoint() {
compose := new(Compose)
rpc.Register(compose)
listener, err := net.Listen("tcp", ":8080")
// error handling omitted
for {
conn, err := listener.Accept()
// error handling omitted
go rpc.ServeConn(conn)
}
}
Client Connection over TCP to Remote RPC function (using Golang so I know it works at least in that respect):
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
client, err := rpc.Dial("tcp", "localhost:8080")
if err != nil {
log.Fatal("dialing:", err)
}
var reply string
e := client.Call("Compose.Details", "my string", &reply)
if e != nil {
log.Fatalf("Something went wrong: %v", e.Error())
}
fmt.Printf("The 'reply' pointer value has been changed to: %s", reply)
}
Here is the Ruby code I'm attempting to use:
require "socket"
require "json"
socket = TCPSocket.new "localhost", "8080"
b = {
method: "Compose.Details",
params: "foobar"
}
socket.write(JSON.dump(b))
resp = JSON.load(socket.readline)
p resp
The reason I think it doesn't work is because the Go RPC requires a pointer to be provided as the second argument to the exposed RPC function but I'm not sure how that works in a different programming language such as Ruby?
I've also tried utilising a library such as https://github.com/chriskite/jimson
require "jimson"
client = Jimson::Client.new("localhost:8080")
result = client.Details("foobar")
But all I get back is:
RestClient::ServerBrokeConnection: Server broke connection
UPDATE
So as indicated by JimB in the comments, I needed to use the JSON RPC library instead. Things still don't quite work, as you'll see...
Here's the updated Ruby client:
require "socket"
require "json"
socket = TCPSocket.new "localhost", "8080"
b = {
method: "Compose.Details",
params: [{ :args => { :foo => "Foo!", :bar => "Bar!" }, :id => "0" }]
}
socket.write(JSON.dump(b))
p JSON.load(socket.readline)
Followed by the updated Go client:
package remote
import "fmt"
// Args is structured around the client's provided parameters
type Args struct {
foo string
bar string
}
// Compose is our RPC functions return type
type Compose string
// Details is our exposed RPC function
func (c *Compose) Details(args *Args, reply *string) error {
fmt.Printf("Args received: %+v\n", args)
*c = "some value"
*reply = "Blah!"
return nil
}
When I run this my Ruby client sees the following:
{"id"=>nil, "result"=>"Blah!", "error"=>nil}
So I'm getting a result back, but if my actual RPC code tried to use the provided args in any way to generate the result then this would be a broken example because the stdout I get from the Go RPC function is a set of empty arg values?
Args received: &{foo: bar:}
I'm not sure why the RPC function isn't able to pull in the arguments I've provided to it?
Also the "id" key: why would I get one back from the RPC function? and how can I give it a meaningful value?
I tried setting an "id" key with a value from my Ruby client but that didn't effect the response from the Go RPC.
答案1
得分: 0
好的,以下是翻译好的内容:
好的,所以我在代码中需要进行的实际修复如下:
传入的参数需要是一个自定义类型,这样你才能导出它,并且该类型内部的字段需要被导出...
type Args struct {
Foo string
Bar string
}
但更重要的是,在Ruby端,你需要以一种满足这个要求的格式发送你的JSON -> https://golang.org/src/net/rpc/jsonrpc/client.go#L45 <- 这样才能让它工作。
所以这样做...
b = {
:method => "Compose.Details",
:params => [{ :Foo => "Foo!", :Bar => "Bar!" }],
:id => "0" # id只是回传给客户端的
}
英文:
OK, so the actual fix I needed to make in my code was as follows:
The incoming args needed to be a custom type so you could export it and the fields themselves within that type need to be exported...
type Args struct {
Foo string
Bar string
}
But more importantly, on the Ruby side, you need to send your JSON in a format that makes this requirement -> https://golang.org/src/net/rpc/jsonrpc/client.go#L45 <- happy
So do this...
b = {
:method => "Compose.Details",
:params => [{ :Foo => "Foo!", :Bar => "Bar!" }],
:id => "0" # id is just echo'ed back to the client
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论