本文是在go1.19.11的版本
server端和client端的代码
//server端代码
type HelloService struct{}
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello:" + request
return nil
}
func main(){
rpc.RegisterName("HelloService", new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
rpc.Accept(listener)
}
//client端的代码
func main(){
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
var reply string
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
从server端分析,首先也是先注册service。
注意,每个结构体都可以通过反射获取他的所有属性和所有方法,每个方法也可以通过反射获取它的输入参数的个数和类型以及返回参数的个数和类型,下面我们看注册代码
func (server *Server) Register(rcvr any) error {
return server.register(rcvr, "", false)
}
// RegisterName is like Register but uses the provided name for the type
// instead of the receiver's concrete type.
func (server *Server) RegisterName(name string, rcvr any) error {
return server.register(rcvr, name, true)
}
func (server *Server) register(rcvr any, name string, useName bool) error {
s := new(service)
s.typ = reflect.TypeOf(rcvr)
s.rcvr = reflect.ValueOf(rcvr)
sname := name
if !useName {
sname = reflect.Indirect(s.rcvr).Type().Name()
}
if sname == "" {
s := "rpc.Register: no service name for type " + s.typ.String()
log.Print(s)
return errors.New(s)
}
if !useName && !token.IsExported(sname) {
s := "rpc.Register: type " + sname + " is not exported"
log.Print(s)
return errors.New(s)
}
s.name = sname
// Install the methods
s.method = suitableMethods(s.typ, logRegisterError)
if len(s.method) == 0 {
str := ""
// To help the user, see if a pointer receiver would work.
method := suitableMethods(reflect.PointerTo(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
}
从上面没有指定service的名字,就通过sname = reflect.Indirect(s.rcvr).Type().Name()反射获取struct的名字,当没指定名字时,struct必须是能导出的(token.IsExported(sname)判断首先字母是否大写),然后调用suitableMethods
func suitableMethods(typ reflect.Type, logErr 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.IsExported() {
continue
}
// Method needs three ins: receiver, *args, *reply.
if mtype.NumIn() != 3 {
if logErr {
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 logErr {
log.Printf("rpc.Register: argument type of method %q is not exported: %q\n", mname, argType)
}
continue
}
// Second arg must be a pointer.
replyType := mtype.In(2)
if replyType.Kind() != reflect.Pointer {
if logErr {
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 logErr {
log.Printf("rpc.Register: reply type of method %q is not exported: %q\n", mname, replyType)
}
continue
}
// Method needs one out.
if mtype.NumOut() != 1 {
if logErr {
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 logErr {
log.Printf("rpc.Register: return type of method %q is %q, must be error\n", mname, returnType)
}
continue
}
methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}
}
return methods
}
suitableMethods的将method的每个方法的反射属性存在其中,其中key是方法的名字,然后检查方法必须要导出(首字母大写),检查输入参数必须是三个,receiver, args, reply.,由于是成员函数会多一个receiver,然后检验args参数的合法性,能导出等等,reply必须是指针,返回参数必须是1个,且是错误的类型。最后将这个service的加入sync.map中。
这个的注册和grpc的注册大致相似,但是有些许不一样,grpc直接加入的是已经生成好的函数指针和方法名字,这个是通过反射获取到它的属性
下一步启动server,rpc.Accept(listener)
func (server *Server) Accept(lis net.Listener) {
for {
conn, err := lis.Accept()
if err != nil {
log.Print("rpc.Serve: accept:", err.Error())
return
}
go server.ServeConn(conn)
}
}
熟悉网络编程的一下子就能看出,一个conn,一个连接。
func (server *Server) ServeConn(conn io.ReadWriteCloser) {
buf := bufio.NewWriter(conn)
srv := &gobServerCodec{
rwc: conn,
dec: gob.NewDecoder(conn),
enc: gob.NewEncoder(buf),
encBuf: buf,
}
server.ServeCodec(srv)
}
func (server *Server) ServeCodec(codec ServerCodec) {
sending := new(sync.Mutex)
wg := new(sync.WaitGroup)
for {
service, mtype, req, argv, replyv, keepReading, err := server.readRequest(codec)
if err != nil {
if debugLog && err != io.EOF {
log.Println("rpc:", err)
}
if !keepReading {
break
}
// send a response if we actually managed to read a header.
//这种情况是读Request成功,然后将args读取的数据丢弃,再给client返回错误信息,并将
//Request放入free链表,for循环继续处理下一个
if req != nil {
server.sendResponse(sending, req, invalidRequest, codec, err.Error())
server.freeRequest(req)
}
continue
}
//如果成功,由于开启了一个 协程wg加1
wg.Add(1)
//开始执行当前的函数
go service.call(server, sending, wg, mtype, req, argv, replyv, codec)
}
// We've seen that there are no more requests.
// Wait for responses to be sent before closing codec.
//出现不可逆的错误,退出上面的循环,要等待当前的执行协程结束,才能结束,关闭编解码
wg.Wait()
codec.Close()
}
然后将conn赋值给gob的编接码接口,因为conn实际本身就是一个输入输出的流,它实现了reader的接口,然后ServeCodec函数里,一个for大循环不停的处理请求。
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)
if err != nil {
//错误,且读到错误,直接返回
if !keepReading {
return
}
//到这里说明,读Request成功,但是还有其他错误,所以也将body的数据读出,返回
// discard body
codec.ReadRequestBody(nil)
return
}
//从前面获取的方法的反射属性,判断args的输入参数的类型,是否是指针,
//通过反射new一个类型的数据
// Decode the argument value.
argIsValue := false // if true, need to indirect before calling.
if mtype.ArgType.Kind() == reflect.Pointer {
argv = reflect.New(mtype.ArgType.Elem())
} else {
argv = reflect.New(mtype.ArgType)
argIsValue = true
}
// argv guaranteed to be a pointer now.
//将args的数据转成interface{},并获取数据
if err = codec.ReadRequestBody(argv.Interface()); err != nil {
return
}
if argIsValue {
argv = argv.Elem()
}
//根据反射new一个reply的回复的数据
replyv = reflect.New(mtype.ReplyType.Elem())
//reply也支持map和slice
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
}
func (server *Server) readRequestHeader(codec ServerCodec) (svc *service, mtype *methodType, req *Request, keepReading bool, err error) {
// Grab the request header.
//从空闲列表中获取一个可用的Request结构体
req = server.getRequest()
//从conn中读取Request的值
err = codec.ReadRequestHeader(req)
//这里的错误包括对端关闭,或者是流的不期待的eof
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.
//读取Request成功,将keepReading设置为true,因为读头成功了,后面还有body要读
keepReading = true
//本例中ServiceMethod="HelloService.Hello"
dot := strings.LastIndex(req.ServiceMethod, ".")
if dot < 0 {
err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod)
return
}
//本例中serviceName="HelloService"
serviceName := req.ServiceMethod[:dot]
//本例中serviceName="Hello"
methodName := req.ServiceMethod[dot+1:]
// Look up the request.
//找到前期注册过的"HelloService"的service
svci, ok := server.serviceMap.Load(serviceName)
if !ok {
err = errors.New("rpc: can't find service " + req.ServiceMethod)
return
}
svc = svci.(*service)
//再在此service中找到"Hello"的方法反射属性
mtype = svc.method[methodName]
if mtype == nil {
err = errors.New("rpc: can't find method " + req.ServiceMethod)
}
return
}
读头的流程都在上面的代码中注释详细说明了,其中的Request用一个freelist管理,从next就能看出这是一个list结构体
type Request struct {
ServiceMethod string // format: "Service.Method"
Seq uint64 // sequence number chosen by client
next *Request // for free list in Server
}
最后直接执行处理函数service.call(server, sending, wg, mtype, req, argv, replyv, codec)
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 {
//当前函数处理完减1
defer wg.Done()
}
mtype.Lock()
//当前函数的调用次数加1
mtype.numCalls++
mtype.Unlock()
//找到函数的方法
function := mtype.method.Func
// Invoke the method, providing a new value for the reply.
//直接调用,注意这里要加一个receiver,多一个
returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})
// The return value for the method is an error.
//返回的类型
errInter := returnValues[0].Interface()
errmsg := ""
if errInter != nil {
errmsg = errInter.(error).Error()
}
//发送返回数据
server.sendResponse(sending, req, replyv.Interface(), codec, errmsg)
//释放,加入free
server.freeRequest(req)
}
func (server *Server) sendResponse(sending *sync.Mutex, req *Request, reply any, codec ServerCodec, errmsg string) {
//Response和Request一样,从free里获取
resp := server.getResponse()
// Encode the response header
resp.ServiceMethod = req.ServiceMethod
if errmsg != "" {
resp.Error = errmsg
reply = invalidRequest
}
//这里的序列号特别重要,client根据这个唯一的序列号知道是否是自己
resp.Seq = req.Seq
sending.Lock()
//conn写入返回的数据
err := codec.WriteResponse(resp, reply)
if debugLog && err != nil {
log.Println("rpc: writing response:", err)
}
sending.Unlock()
//用完加入free
server.freeResponse(resp)
}
上面代码中的注释,基本上把rpc分析完毕,rpc的server很简单,用gob格式读取数据(有兴趣的可以去看看gob读取数据的格式),没有grpc的chain的函数洋葱模型,而且调用函数时,rpc是根据反射来new的args和reply,不像grpc直接生成好对应的结构体,直接给函数地址就好了,这点上grpc性能要好一点,但是rpc有更大的灵活性,可以根据它修改成参数和返回值是自定义的,毕竟实现的少。
rpc还是很简单的,rpc的client让读者自己去看
最后本人能力有限,如果上面的有纰漏和问题,欢迎大佬指出