Golang原生rpc(rpc服务端源码解读)


Posted in Golang onApril 07, 2022

创建rpc接口,需要几个条件

  • 方法的类型是可输出的
  • 方法的本身也是可输出的
  • 方法必须有两个参数,必须是输出类型或者是内建类型
  • 方法的第二个参数是指针类型
  • 方法返回的类型为error

rpc服务原理分析

server端

  • 服务注册
  • 处理网络调用

服务注册 通过反射处理,将接口存入到map中,进行调用 注册服务两个方法

func Register (rcvr interface{}) error {}
func RegisterName (rcvr interface{} , name string) error {}
//指定注册的名称

注册方法的源代码解读 首先,无论是Register还是RegisterName底层代码都是调用register方法,进行服务注册。 server.go register方法解读

func (server *Server) register(rcvr interface{}, name string, useName bool) error {
	//创建一个service实例
	s := new(service)
	s.typ = reflect.TypeOf(rcvr)
	s.rcvr = reflect.ValueOf(rcvr)
	sname := reflect.Indirect(s.rcvr).Type().Name()
	//如果服务名为空,则使用默认的服务名
	if useName {
		sname = name
	}
	if sname == "" {
		s := "rpc.Register: no service name for type " + s.typ.String()
		log.Print(s)
		return errors.New(s)
	}
	//判断方法名是否暴漏的,如果方法名不是暴露的,则会导致调用不成功,所以返回false
	if !token.IsExported(sname) && !useName {
		s := "rpc.Register: type " + sname + " is not exported"
		log.Print(s)
		return errors.New(s)
	}
	s.name = sname

	// Install the methods
	//调用suitableMethods函数,进行返回接口,在suitableMethods中判断方法是否符合作为rpc接口的条件,如果符合,则进行添加到services中
	s.method = suitableMethods(s.typ, true)

	if len(s.method) == 0 {
		str := ""

		// To help the user, see if a pointer receiver would work.
		//如果方法绑定到结构体的地址上,使用reflect.TypeOf()是不会发现方法的,所以也要进行查找绑定到结构体地址上的方法
		method := suitableMethods(reflect.PtrTo(s.typ), false)
		if len(method) != 0 {
			str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
		} else {
			str = "rpc.Register: type " + sname + " has no exported methods of suitable type"
		}
		log.Print(str)
		return errors.New(str)
	}
	//判断服务接口是否已经注册。
	if _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
		return errors.New("rpc: service already defined: " + sname)
	}
	return nil
}

suitableMethod方法解读

func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType {
	//创建一个方法的切片
	methods := make(map[string]*methodType)
	for m := 0; m < typ.NumMethod(); m++ {
		method := typ.Method(m)
		mtype := method.Type
		mname := method.Name
		// Method must be exported.
		if method.PkgPath != "" {
			continue
		}
		// Method needs three ins: receiver, *args, *reply.
		//如果传入的参数,不为三个,则会报错,这里为什么是三个?
		//golang方法体中默认传入结构体实例,所以request,*response,结构体实例一共三个参数
		if mtype.NumIn() != 3 {
			if reportErr {
				log.Printf("rpc.Register: method %q has %d input parameters; needs exactly three\n", mname, mtype.NumIn())
			}
			continue
		}
		// First arg need not be a pointer.
		argType := mtype.In(1)
		if !isExportedOrBuiltinType(argType) {
			if reportErr {
				log.Printf("rpc.Register: argument type of method %q is not exported: %q\n", mname, argType)
			}
			continue
		}
		// Second arg must be a pointer.
		//判断第二个参数是否为指针,如果不为指针,则返回false。
		replyType := mtype.In(2)
		if replyType.Kind() != reflect.Ptr {
			if reportErr {
				log.Printf("rpc.Register: reply type of method %q is not a pointer: %q\n", mname, replyType)
			}
			continue
		}
		// Reply type must be exported.
		if !isExportedOrBuiltinType(replyType) {
			if reportErr {
				log.Printf("rpc.Register: reply type of method %q is not exported: %q\n", mname, replyType)
			}
			continue
		}
		// Method needs one out.
		//返回结果是否为一个值,且为error
		if mtype.NumOut() != 1 {
			if reportErr {
				log.Printf("rpc.Register: method %q has %d output parameters; needs exactly one\n", mname, mtype.NumOut())
			}
			continue
		}
		// The return type of the method must be error.
		if returnType := mtype.Out(0); returnType != typeOfError {
			if reportErr {
				log.Printf("rpc.Register: return type of method %q is %q, must be error\n", mname, returnType)
			}
			continue
		}
		//将接口加入service
		methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}
	}
	return methods
}

接收到请求后会不断的解析请求 解析请求的两个方法 readRequestHeader

func (server *Server) readRequestHeader(codec ServerCodec) (svc *service, mtype *methodType, req *Request, keepReading bool, err error) {
	// Grab the request header.
	//接收到请求,对请求进行编码
	req = server.getRequest()
	err = codec.ReadRequestHeader(req)
	if err != nil {
		req = nil
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return
		}
		err = errors.New("rpc: server cannot decode request: " + err.Error())
		return
	}

	// We read the header successfully. If we see an error now,
	// we can still recover and move on to the next request.
	keepReading = true
//编码后的请求,进行间隔,所以只要进行将.的左右两边的数据进行分割,就能解码
	dot := strings.LastIndex(req.ServiceMethod, ".")
	if dot < 0 {
		err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod)
		return
	}
	serviceName := req.ServiceMethod[:dot]
	methodName := req.ServiceMethod[dot+1:]

	// Look up the request.
	svci, ok := server.serviceMap.Load(serviceName)
	if !ok {
		err = errors.New("rpc: can't find service " + req.ServiceMethod)
		return
	}
	svc = svci.(*service)
	//获取到注册服务时,注册的接口
	mtype = svc.method[methodName]
	if mtype == nil {
		err = errors.New("rpc: can't find method " + req.ServiceMethod)
	}
	return
}

readRequest方法

func (server *Server) readRequest(codec ServerCodec) (service *service, mtype *methodType, req *Request, argv, replyv reflect.Value, keepReading bool, err error) {
	service, mtype, req, keepReading, err = server.readRequestHeader(codec)
//调用上面的readRequestHeader方法,进行解码,并返返回接口数据
	if err != nil {
		if !keepReading {
			return
		}
		// discard body
		codec.ReadRequestBody(nil)
		return
	}

	// Decode the argument value.
	argIsValue := false // if true, need to indirect before calling.
	//判断传擦是否为指针,如果为指针,需要使用Elem()方法,进行指向结构体
	if mtype.ArgType.Kind() == reflect.Ptr {
		argv = reflect.New(mtype.ArgType.Elem())
	} else {
		argv = reflect.New(mtype.ArgType)
		argIsValue = true
	}
	// argv guaranteed to be a pointer now.
	if err = codec.ReadRequestBody(argv.Interface()); err != nil {
		return
	}
	if argIsValue {
		argv = argv.Elem()
	}

	replyv = reflect.New(mtype.ReplyType.Elem())

	switch mtype.ReplyType.Elem().Kind() {
	case reflect.Map:
		replyv.Elem().Set(reflect.MakeMap(mtype.ReplyType.Elem()))
	case reflect.Slice:
		replyv.Elem().Set(reflect.MakeSlice(mtype.ReplyType.Elem(), 0, 0))
	}
	return
}

call方法

func (s *service) call(server *Server, sending *sync.Mutex, wg *sync.WaitGroup, mtype *methodType, req *Request, argv, replyv reflect.Value, codec ServerCodec) {
	if wg != nil {
		defer wg.Done()
	}
	mtype.Lock()
	mtype.numCalls++
	mtype.Unlock()
	function := mtype.method.Func
	// Invoke the method, providing a new value for the reply.
	//调用call方法,并将参数转化为valueof型参数,
	returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})
	// The return value for the method is an error.
	//将返回的error进行读取,转化为interface{}型
	errInter := returnValues[0].Interface()
	errmsg := ""
	if errInter != nil {
	//将error进行断言
		errmsg = errInter.(error).Error()
	}
	server.sendResponse(sending, req, replyv.Interface(), codec, errmsg)
	server.freeRequest(req)
}

注册的大概流程

  • 根据反射,进行接口的获取
  • 使用方法判断接口是否符合作为rpc接口的规范(有两个参数,第二个参数为指针,返回一个参数error)
  • 如果不符合规范,将返回error,符合规范,将存入map,进行提供调用

接收请求的大概流程

  • 首先,不断的接收数据流,并进行解码,解码之后为data.data,所以我们需要使用 . 作为分隔符,进行数据的截切和读取
  • 将读取的数据在注册的map中进行查找,如果查找到,返回相关的service和其他数据
  • 进行调用

到此这篇关于Golang原生rpc(rpc服务端源码解读)的文章就介绍到这了,更多相关Golang原生rpc内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Golang 相关文章推荐
Go Gin实现文件上传下载的示例代码
Apr 02 Golang
golang中的空slice案例
Apr 27 Golang
golang 如何通过反射创建新对象
Apr 28 Golang
go设置多个GOPATH的方式
May 05 Golang
解决goland 导入项目后import里的包报红问题
May 06 Golang
golang中的并发和并行
May 08 Golang
go语言基础 seek光标位置os包的使用
May 09 Golang
Go timer如何调度
Jun 09 Golang
Go遍历struct,map,slice的实现
Jun 13 Golang
Go Plugins插件的实现方式
Aug 07 Golang
golang 语言中错误处理机制
Aug 30 Golang
golang定时器
Apr 14 Golang
Go并发4种方法简明讲解
Go归并排序算法的实现方法
Apr 06 #Golang
golang操作rocketmq的示例代码
Apr 06 #Golang
victoriaMetrics库布隆过滤器初始化及使用详解
如何解决goland,idea全局搜索快捷键失效问题
golang为什么要统一错误处理
简单聊聊Golang中defer预计算参数
Mar 25 #Golang
You might like
我的群发邮件程序
2006/10/09 PHP
jQuery.getScript加载同域JS的代码
2012/02/13 Javascript
用jquery方法操作radio使其默认选项是否
2013/09/10 Javascript
动态加载iframe时get请求传递中文参数乱码解决方法
2014/05/07 Javascript
js跨域请求数据的3种常用的方法
2015/12/01 Javascript
jQuery prototype冲突的2种解决方法(附demo示例下载)
2016/01/21 Javascript
Nodejs中 npm常用命令详解
2016/07/04 NodeJs
Angular 中 select指令用法详解
2016/09/29 Javascript
JS+canvas画一个圆锥实例代码
2017/12/13 Javascript
Node.js使用cookie保持登录的方法
2018/05/11 Javascript
解决Nuxt使用axios跨域问题
2020/07/06 Javascript
从源码角度来回答keep-alive组件的缓存原理
2021/01/18 Javascript
[07:52]2014DOTA2 TI逗比武士游V社解说背后的故事
2014/07/10 DOTA
python通过shutil实现快速文件复制的方法
2015/03/14 Python
Python中functools模块的常用函数解析
2016/06/30 Python
python搭建服务器实现两个Android客户端间收发消息
2018/04/12 Python
Python3.4学习笔记之 idle 清屏扩展插件用法分析
2019/03/01 Python
Python基础之循环语句用法示例【for、while循环】
2019/03/23 Python
Pytorch根据layers的name冻结训练方式
2020/01/06 Python
Django 实现对已存在的model进行更改
2020/03/28 Python
Python正则表达式高级使用方法汇总
2020/06/18 Python
英国领先的新鲜松露和最好的松露产品供应商:TruffleHunter
2019/08/26 全球购物
ABOUT YOU罗马尼亚:超过600个时尚品牌
2019/09/19 全球购物
茱莉蔻美国官网:Jurlique美国
2020/11/24 全球购物
硕士研究生个人求职信
2013/12/04 职场文书
调查研究项目计划书
2014/04/29 职场文书
我的梦想演讲稿
2014/04/30 职场文书
医疗器械售后服务承诺书
2014/05/21 职场文书
大跃进口号
2014/06/16 职场文书
本科生自荐信
2014/06/18 职场文书
孕妇离婚协议书范本
2014/11/20 职场文书
地方课程教学计划
2015/01/19 职场文书
学期个人自我总结
2015/02/13 职场文书
公务员处分决定书
2015/06/25 职场文书
2019年度开业庆典祝福语大全!
2019/07/05 职场文书
深入理解java.lang.String类的不可变性
2021/06/27 Java/Android