以下是五个对应的rpc的简单的客户端和服务器端,分别涉及到tcp链接、tcp链接封装、jsonrpc、httprpc和json+http+rpc,其实也就是基础学习的五个过程
一、tcp连接
服务器端
package main
import (
"log"
"net"
"net/rpc"
)
func main() {
// 这个是rpc的服务端口
// 我这边得到的信息是rpc可以通过http或者tcp进行实现
// 注册一下这个任务,第一个参数类似于命名空间,第二个参数是定义的结构体
err := rpc.RegisterName("HelloService", new(HelloService))
if err != nil {
log.Fatal("注册失败:", err)
}
// 开启Tcp监听,获取监听端的实例
listener, err := net.Listen("tcp", "localhost:1234")
if err != nil {
log.Fatal("tcp监听失败:", err)
}
// 这边官网是建议使用一个for循环来实现一直监听Accept,我尝试了一下不是调用问题,而是调用完成之后这边就会关闭
// con, err := listener.Accept()
// if err != nil {
// log.Fatal("Accept error:", err)
// }
// rpc.ServeConn(con)
// 尝试使用一个for循环来解决这个问题,这样的for循环的方式确实不会在请求完毕之后关闭
for {
con, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
go func() {
rpc.ServeConn(con)
}()
}
}
// 定义rpc的服务结构
type HelloService struct{}
// 定义方法Hello
// 对于rpc的方法有以下的规定
// 方法必须暴露
// 承载方法的类型必须暴露
// 方法的两个参数必须暴露
// 第二个参数必须是指针类型
// 方法必须return一个错误
// func (t *T) MethodName(argType T1, replyType *T2) error
// 第一个参数是就是参数,第二个参数实际上是返回值
func (h *HelloService) Hello(request string, replay *string) error {
*replay = "hello" + request
return nil
}
客户端
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
// 这个是rpc的请求部分
// 尝试拨号去调用其他服务器另外的方法
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("拨号失败:", err)
}
// 创建replay的指针
var reply1 string
err = client.Call("HelloService.Hello", " world", &reply1)
// 第一个参数是对应结构体的方法,第二个是参数(任何类型),第三个是replay的指针
if err != nil {
log.Fatal("调用失败:", err)
}
// 打印出我们想要的值
fmt.Println("first", reply1) //成功
// 尝试调用两次
var reply2 string
err = client.Call("HelloService.Hello", " huangfeng", &reply2)
// 第一个参数是对应结构体的方法,第二个是参数(任何类型),第三个是replay的指针
if err != nil {
log.Fatal("调用失败:", err)
}
// 打印出我们想要的值
fmt.Println("second", reply2) //能实现调用两次
}
二、tcp连接的封装
服务器端
package main
import (
"log"
"net"
"net/rpc"
)
func main() {
// 这是第一步的对服务器的包装
// 注册实例
err := RegisterHelloService(new(HelloService))
if err != nil {
log.Fatal("注册出现问题", err)
}
// 开始第二个方法的注册,注意注册一定需要传入一个指针
err = RegisterAddService(&AddService{v: 10})
if err != nil {
log.Fatal("注册出现问题", err)
}
// 开启tcp监听
listener, err := net.Listen("tcp", "localhost:1234")
if err != nil {
log.Fatal("开启监听出现问题", err)
}
// 用一个for循环进行监听
for {
con, err := listener.Accept()
if err != nil {
log.Fatal("接收出现问题:", err)
}
go func() {
defer con.Close() //添加了这个close
rpc.ServeConn(con)
}()
}
}
// 尝试在这里创建第二个接口
type AddServiceInteface interface {
Add(requestArg int, reply *int) error
}
// 第二个接口对应的注册方式
func RegisterAddService(example AddServiceInteface) error {
return rpc.RegisterName("AddService", example)
}
// 定义实例
type AddService struct {
v int
}
// 挂载方法
func (a *AddService) Add(requestArg int, reply *int) error {
*reply = a.v + requestArg
return nil
}
// 创建接口
type HelloServiceInterface interface {
Hello(requestArg string, reply *string) error
}
// 创建对应接口的注册方法,我觉得这里其实有过度封装的嫌疑
func RegisterHelloService(example HelloServiceInterface) error {
return rpc.RegisterName("HelloService", example)
} // 这里的这个return的方法有点意思,返回值都是error
// 创建一个符合HelloServiceInterface结构体
type HelloService struct {
}
// 创建对应的方法
func (h *HelloService) Hello(requestArg string, reply *string) error {
*reply = "hello," + requestArg
return nil
}
客户端
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
// 这一部分是关于拨号等问题的封装,这一部分和rpcServer2部分互相对应
clientPt, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("拨号失败:", err)
}
// defer
defer func() {
clientPt.Close()
fmt.Println("关闭共同client")
}()
// 调用自己创建的方法
hc := HelloServiceClient{clientPt}
reply1, err := hc.Hello("黄烽")
if err != nil {
log.Fatal("call fail:", err)
}
fmt.Println("helloCall:", reply1)
// 调用第二个方法
ac := AddServiceClient{clientPt}
reply2, err := ac.Add(1000)
if err != nil {
log.Fatal("call fail:", err)
}
fmt.Println("addCall:", reply2)
// 当然这边的client可以单独封装,也就是这个拨号可以单独封装,我们这里是使用了一个通用的client只进行了一次拨号
}
// 封装一下client
type HelloServiceClient struct {
*rpc.Client
}
// 创建callHello的方法
func (h HelloServiceClient) Hello(requestArg string) (reply string, err error) {
err = h.Call("HelloService.Hello", requestArg, &reply)
return
}
// 另外一个client
type AddServiceClient struct {
*rpc.Client
}
// 创建Add方法
func (a AddServiceClient) Add(requestArg int) (reply int, err error) {
err = a.Call("AddService.Add", requestArg, &reply)
return
}
三、jsonrpc
服务器端
package main
import (
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
func main() {
// 使用jsonrpc创建一个跨语言的rpc
err := rpc.RegisterName("HelloService", new(HelloService))
if err != nil {
log.Fatal("注册失败:", err)
}
listener, err := net.Listen("tcp", "localhost:1234")
if err != nil {
log.Fatal("tcp监听失败:", err)
}
for {
con, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
go func() {
defer con.Close()
rpc.ServeCodec(jsonrpc.NewServerCodec(con)) //主要是这边发生了变化
// jsonrpc.NewServerCodec(con) 实际上是根据con创建了一个编码器,通过这个编码器,可以实现跨语言的rpc
// 这个编码器实际上依赖的是json
}()
}
}
// 定义rpc的服务结构
type HelloService struct{}
func (h *HelloService) Hello(request string, replay *string) error {
*replay = "hello" + request
return nil
}
客户端
package main
import (
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
func main() {
// 这个是rpcServer3的客户端
// 主要是用来测试json-rpc
con, err := net.Dial("tcp", "localhost:1234") //这边使用的是net拨号
// con其实就是一个读写close体
if err != nil {
log.Fatal("net拨号失败:", err)
}
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(con)) //将con转换为clientCodec,然后转变成rpc的client
// 后面就一样了
var replay string
err = client.Call("HelloService.Hello", "huangfeng", &replay)
if err != nil {
log.Fatal("call fail:", err)
}
fmt.Println("这个是reply", replay)
}
四、http-rpc
服务器端
package main
import (
"fmt"
"net"
"net/http"
"net/rpc"
)
func main() {
// 这边采取的是实例化自定义server的方式,为的就是自定义路径
// 这边实际上采用的是http的通信,但是依旧是gob独有的解码方式,下一步将实现http和jsonrpc结合的方式
server := rpc.NewServer()
err := server.RegisterName("HelloService", new(HelloService))
if err != nil {
fmt.Println(err)
}
server.HandleHTTP("/gorpc", "/debug")
l, err := net.Listen("tcp", "localhost:1234")
if err != nil {
fmt.Println(err)
}
go http.Serve(l, nil)
}
// 创建接口
type HelloServiceInterface interface {
Hello(requestArg string, reply *string) error
}
// 创建一个符合HelloServiceInterface结构体
type HelloService struct {
}
// 创建对应的方法
func (h *HelloService) Hello(requestArg string, reply *string) error {
*reply = "hello," + requestArg
return nil
}
客户端
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
// client, err := rpc.DialHTTP("tcp", "localhost:1234")
// 这个拨号实际上是拨通的默认路径,但是我们在server当中重设了路径
// 这里我们要采取更准确的拨号路径
client, err := rpc.DialHTTPPath("tcp", "localhost:1234", "/gorpc")
if err != nil {
log.Fatal("dail fail:", err)
}
var reply string
client.Call("HelloService.Hello", "huangfeng", &reply)
fmt.Println(reply)
}
五、json+http+rpc
服务器端
package main
import (
"io"
"net/http"
"net/rpc"
"net/rpc/jsonrpc"
)
func main() {
// 实现注册
rpc.RegisterName("HelloService", new(HelloService))
// 注册一下处理方法,意思是,但开头是/jsonrpc的时候就采用后面的处理方程
http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
var conn io.ReadWriteCloser = struct {
io.Writer
io.ReadCloser
}{
Writer: w,
ReadCloser: r.Body,
}
rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
})
// 监听
http.ListenAndServe("localhost:1234", nil)
}
// 创建接口
type HelloServiceInterface interface {
Hello(requestArg string, reply *string) error
}
// 创建一个符合HelloServiceInterface结构体
type HelloService struct {
}
// 创建对应的方法
func (h *HelloService) Hello(requestArg string, reply *string) error {
*reply = "hello," + requestArg
return nil
}
客户端
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
func main() {
// 测试连接一下json+http的连接
// 首先在postman上面测试这个连接,成功,{"method":"HelloService.Hello","params":["hello"],"id":0} 这是对应的参数
// {"id":0,"result":"hello,hello","error":null} 这是对应的返回值
// 我现在想从客户端使用http发出请求
str := `{"method":"HelloService.Hello","params":["hello"],"id":0}`
body := strings.NewReader(str)
r, err := http.Post("http://localhost:1234/jsonrpc", "application/json", body)
if err != nil {
log.Fatal("失败:", err)
}
buf := make([]byte, r.ContentLength)
r.Body.Read(buf)
fmt.Println(string(buf)) //{"id":0,"result":"hello,hello","error":null}
// 虽然这个简陋,但是也成功了
}
以上就是五对例子,包含提供服务和调用服务,写的毛糙的话多多包涵