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 相关文章推荐
Golang二维切片初始化的实现
Apr 08 Golang
golang 定时任务方面time.Sleep和time.Tick的优劣对比分析
May 05 Golang
goland 设置project gopath的操作
May 06 Golang
解决golang 关于全局变量的坑
May 06 Golang
go xorm框架的使用
May 22 Golang
试了下Golang实现try catch的方法
Jul 01 Golang
Golang表示枚举类型的详细讲解
Sep 04 Golang
深入理解go缓存库freecache的使用
Feb 15 Golang
详解Golang如何优雅的终止一个服务
Mar 21 Golang
Golang 实现 WebSockets 之创建 WebSockets
Apr 24 Golang
Golang入门之计时器
May 04 Golang
详解Go语言中配置文件使用与日志配置
Jun 01 Golang
Go并发4种方法简明讲解
Go归并排序算法的实现方法
Apr 06 #Golang
golang操作rocketmq的示例代码
Apr 06 #Golang
victoriaMetrics库布隆过滤器初始化及使用详解
如何解决goland,idea全局搜索快捷键失效问题
golang为什么要统一错误处理
简单聊聊Golang中defer预计算参数
Mar 25 #Golang
You might like
在windows iis5下安装php4.0+mysql之我见
2006/10/09 PHP
在PHP3中实现SESSION的功能(一)
2006/10/09 PHP
IIS下PHP连接数据库提示mysql undefined function mysql_connect()
2010/06/04 PHP
Yii2.0高级框架数据库增删改查的一些操作
2015/11/16 PHP
PHP实现实时生成并下载超大数据量的EXCEL文件详解
2017/10/23 PHP
PHP PDOStatement::setFetchMode讲解
2019/02/03 PHP
AJAX使用了UpdatePanel后无法使用alert弹出脚本
2010/04/02 Javascript
img onload事件绑定各浏览器均可执行
2012/12/19 Javascript
『jQuery』取指定url格式及分割函数应用
2013/04/22 Javascript
开源的javascript项目Kissy介绍
2014/11/28 Javascript
javascript文件加载管理简单实现方法
2015/07/25 Javascript
JavaScript实现将文本框的值插入指定位置的方法
2015/08/13 Javascript
IE7浏览器窗口大小改变事件执行多次bug及IE6/IE7/IE8下resize问题
2015/08/21 Javascript
JSONP跨域请求实例详解
2016/07/04 Javascript
AngularJS之依赖注入模拟实现
2016/08/19 Javascript
jQuery实现的表格前端排序功能示例
2017/09/18 jQuery
全面介绍vue 全家桶和项目实例
2017/12/27 Javascript
详解webpack 入门与解析
2018/04/09 Javascript
[03:24]DOTA2超级联赛专访hao 大翻盘就是逆袭
2013/05/24 DOTA
[50:11]2018DOTA2亚洲邀请赛 4.7总决赛 LGD vs Mineski 第三场
2018/04/09 DOTA
python中 ? : 三元表达式的使用介绍
2013/10/09 Python
Python计算字符宽度的方法
2016/06/14 Python
pycharm中显示CSS提示的知识点总结
2019/07/29 Python
python基础 range的用法解析
2019/08/23 Python
Python3 JSON编码解码方法详解
2019/09/06 Python
python调用matplotlib模块绘制柱状图
2019/10/18 Python
TensorFlow dataset.shuffle、batch、repeat的使用详解
2020/01/21 Python
美国大码时尚女装购物网站:ELOQUII
2017/12/28 全球购物
State Cashmere官网:半零售价可持续蒙古羊绒
2020/02/26 全球购物
法律顾问服务方案
2014/05/15 职场文书
教师党的群众路线教育实践活动个人整改方案
2014/10/31 职场文书
党员倡议书
2015/01/19 职场文书
食堂采购员岗位职责
2015/04/03 职场文书
2015年高校保卫处工作总结
2015/07/23 职场文书
Nginx配置Https安全认证的实现
2021/05/26 Servers
基于Redis结合SpringBoot的秒杀案例详解
2021/10/05 Redis