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的区别也不是那么清晰和明白。