简单说说net.rpc

Overview
net.RPC并不是一个复杂的包,并且其示例代码已经将基本用法展示得非常清楚。但是在阅读RPC的文档,对其所有的功能一一探究时却产生了不少疑惑。比如,ServeCodec, ServeRequest, ServeConn的区别等。单看文档是无法把这些疑惑搞清楚的,只有深入代码才能弄明白它们之间的区别。
 
ServeCodec和ServeConn
 
先看一下两个函数的声明:
func ServeConn(conn io.ReadWriteCloser)
func ServeCodec(codec ServerCodec)
从调用关系来看ServeConn最终会调用ServeCodec来解析从客户端发来的调用请求。这一点其实很容易理解,必须先要建立连接,然后才会有数据用来解析。但这两个函数放在一起,单看文档的话着实让人困惑,因为这两个函数的作用是完全一样的:都是用来接收和处理客户端的调用请求的。
写惯了C++和看惯了C#的代码,从我的正常思维来理解的话,应该有一个类似SetCodec这样的函数。这样用户可以调用SetCodec来设置codec,然后再调用ServeConn来处理调用请求。在ServeConn里面先从缓冲区里面读取数据,然后传给codec做解析,再根据codec的返回结果决定要调用哪个注册service,最后把调用结果返回给客户端。
然而现实是却只有一个奇怪的ServeCodec。既然ServeCodec与ServeConn具有相同的作用,那么ServeCodec就不只是简单地设置一个codec而已,它应该包括一个完整的处理流程。问题是ServeCodec的数据来自哪里呢?从函数声明上可以看出ServeConn的数据肯定来自于它的传入参数,难道ServeCodec的数据也来自于它的参数?
先来看一下ServerCodec的声明:
<type ServerCodec interface {
    ReadRequestHeader(*Request) error
    ReadRequestBody(interface{}) error
    WriteResponse(*Response, interface{}) error
    Close() error
}

从ServerCodec的接口声明上可以看出,一个ServerCodec已经包含了我们希望一个connection所能完成的所有事情。ReadRequestHeader读取报头,ReadRequestBody读取内容,而RPC的调用结果则通过WriteResponse返回给调用者。也就是说ServerCodec的实现者不仅要传来的数据解析出来,还要负责从connection中读写数据。net/rpc默认采用encoding/gob编解码数据,src/pkg/net/rpc/server.go文件中gobServerCodec实现了这些功能:
type gobServerCodec struct {
rwc    io.ReadWriteCloser
dec    *gob.Decoder
enc    *gob.Encoder
encBuf *bufio.Writer
}
gobServerCodec封装了gob编解码器,还有一个ReadWriteCloser接口。注意到ServeConn的传入参数也是ReadWriteClose类型的,ServeConn函数的实现完美演示了gobServerCodec的用法:
func (server *Server) ServeConn(conn io.ReadWriteCloser) {
buf := bufio.NewWriter(conn)
srv := &gobServerCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(buf), buf}
server.ServeCodec(srv)
}
我们再贴出Conn的部分声明:
type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    ......
}
可以看到Conn实现了io.ReadWriteCloser接口。也就是说用户将connection传给了ServeConn,而ServeConn则把connection又传给了gobServerCodec。至此豁然开朗,ServerCodec的实现必须包含对connection的封装,全部的关键点就在gobServerCodec的使用方式上。
 
ServeRequest:
ServeCodec里面是个死循环,一直不停地从客户端读数据。如果没有数据就挂在那里。ServeRequest则只读一次,然后把连接关闭。
 
ServeHTTP:
ServeHTTP其实是个回调,实现了http.Handler接口。当调用HandleHTTP的时候,会把ServeHTTP注册给http包。有数据来的时候,ServerHTTP就会被http服务器调用。这个接口不需要用户直接调用,而是要和HandleHTTP配合来使用。还有一点要注意,要用http.Serve()启动http服务器来监听用户请求。
 
总结:
RPC里面的实现比较混乱,各种功能杂揉在一起,想要把这些接口都弄清楚,必须深入到源代码里面。幸亏GO语言首选的分发方式为源代码方式,否则仅凭RPC的文档水平是不能很好地支持开发的。Duck-typing确实很灵活,灵活到会有摸不着头脑的情况出现。若不熟悉net.http很容易就被ServeHTTP搞迷糊了。ServeConn和ServeCodec的区别也不是那么清晰和明白。

 

posted @ 2013-12-01 22:21  hugh lo  阅读(745)  评论(0编辑  收藏  举报