golang关于rpc的五对基础client和server的例子

以下是五个对应的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}

	// 虽然这个简陋,但是也成功了
}

以上就是五对例子,包含提供服务和调用服务,写的毛糙的话多多包涵

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值