这篇文章介绍的transport不是我们学习的重点,因为你可能用不到他,但是作为学习go-micro的一部分,还是得提一下。
transport用于服务间通信,基于socket的send/recv语义。其接口的方法集如下:
type Transport interface {
Init(...Option) error
Options() Options
Dial(addr string, opts ...DialOption) (Client, error)
Listen(addr string, opts ...ListenOption) (Listener, error)
String() string
}
var (
DefaultTransport Transport = NewHTTPTransport()
DefaultDialTimeout = time.Second * 5
)
主要的作用体现在Dial和Listen方法中,
func (h *httpTransport) Dial(addr string, opts ...DialOption) (Client, error) {
dopts := DialOptions{
Timeout: DefaultDialTimeout,
}
for _, opt := range opts {
opt(&dopts)
}
var conn net.Conn
var err error
// TODO: support dial option here rather than using internal config
if h.opts.Secure || h.opts.TLSConfig != nil {
config := h.opts.TLSConfig
if config == nil {
config = &tls.Config{
InsecureSkipVerify: true,
}
}
config.NextProtos = []string{"http/1.1"}
conn, err = newConn(func(addr string) (net.Conn, error) {
return tls.DialWithDialer(&net.Dialer{Timeout: dopts.Timeout}, "tcp", addr, config)
})(addr)
} else {
conn, err = newConn(func(addr string) (net.Conn, error) {
return net.DialTimeout("tcp", addr, dopts.Timeout)
})(addr)
}
if err != nil {
return nil, err
}
return &httpTransportClient{
ht: h,
addr: addr,
conn: conn,
buff: bufio.NewReader(conn),
dialOpts: dopts,
r: make(chan *http.Request, 1),
local: conn.LocalAddr().String(),
remote: conn.RemoteAddr().String(),
}, nil
}
在net/http源码中,我们也学习过Dial方法,他的作用是连接服务端,并返回一个代表连接的conn.
其实这里也是差不多的,只不过代表连接的conn放在一个client结构体中,这个结构体实现了transport.Client接口
type Socket interface {
Recv(*Message) error
Send(*Message) error
Close() error
Local() string
Remote() string
}
type Client interface {
Socket
}
Listen方法根据addr和ListenOption参数进行了处理,然后组装成httpTransportListener。该结构体实现了
type Listener interface {
Addr() string
Close() error
Accept(func(Socket)) error
}
func (h *httpTransport) Listen(addr string, opts ...ListenOption) (Listener, error) {
var options ListenOptions
for _, o := range opts {
o(&options)
}
var l net.Listener
var err error
// TODO: support use of listen options
if h.opts.Secure || h.opts.TLSConfig != nil {
config := h.opts.TLSConfig
fn := func(addr string) (net.Listener, error) {
if config == nil {
hosts := []string{addr}
// check if its a valid host:port
if host, _, err := net.SplitHostPort(addr); err == nil {
if len(host) == 0 {
hosts = maddr.IPs()
} else {
hosts = []string{host}
}
}
// generate a certificate
cert, err := mls.Certificate(hosts...)
if err != nil {
return nil, err
}
config = &tls.Config{Certificates: []tls.Certificate{cert}}
}
return tls.Listen("tcp", addr, config)
}
l, err = mnet.Listen(addr, fn)
} else {
fn := func(addr string) (net.Listener, error) {
return net.Listen("tcp", addr)
}
l, err = mnet.Listen(addr, fn)
}
if err != nil {
return nil, err
}
return &httpTransportListener{
ht: h,
listener: l,
}, nil
}
在httpTransportListener的Accept方法中,启动了一个http server来监听请求并处理。
func (h *httpTransportListener) Accept(fn func(Socket)) error {
// create handler mux
mux := http.NewServeMux()
// register our transport handler
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var buf *bufio.ReadWriter
var con net.Conn
// read a regular request
if r.ProtoMajor == 1 {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
r.Body = ioutil.NopCloser(bytes.NewReader(b))
// hijack the conn
hj, ok := w.(http.Hijacker)
if !ok {
// we're screwed
http.Error(w, "cannot serve conn", http.StatusInternalServerError)
return
}
conn, bufrw, err := hj.Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer conn.Close()
buf = bufrw
con = conn
}
// buffered reader
bufr := bufio.NewReader(r.Body)
// save the request
ch := make(chan *http.Request, 1)
ch <- r
// create a new transport socket
sock := &httpTransportSocket{
ht: h.ht,
w: w,
r: r,
rw: buf,
buf: bufr,
ch: ch,
conn: con,
local: h.Addr(),
remote: r.RemoteAddr,
closed: make(chan bool),
}
// execute the socket
fn(sock)
})
// get optional handlers
if h.ht.opts.Context != nil {
handlers, ok := h.ht.opts.Context.Value("http_handlers").(map[string]http.Handler)
if ok {
for pattern, handler := range handlers {
mux.Handle(pattern, handler)
}
}
}
// default http2 server
srv := &http.Server{
Handler: mux,
}
// insecure connection use h2c
if !(h.ht.opts.Secure || h.ht.opts.TLSConfig != nil) {
srv.Handler = h2c.NewHandler(mux, &http2.Server{})
}
// begin serving
return srv.Serve(h.listener)
}
在Accept方法中,启动的Server劫持了客户端请求,并重新创建了一个httpTransportSocket,然后根据Accept方法的函数参数去执行该socket.
我们自定义一个服务,来测试一下,如何使用transport.
// Package main
package main
import (
"context"
"time"
hello "github.com/asim/go-micro/examples/v3/greeter/srv/proto/hello"
"github.com/asim/go-micro/v3"
"github.com/asim/go-micro/v3/util/log"
"google.golang.org/grpc"
)
type Say struct{}
func (s *Say) Hello(ctx context.Context, req *hello.Request, rsp *hello.Response) error {
log.Log("Received Say.Hello request")
rsp.Msg = "Hello " + req.Name
return nil
}
func main() {
go func() {
for {
grpc.DialContext(context.TODO(), "127.0.0.1:9091")
time.Sleep(time.Second)
}
}()
service := micro.NewService(
micro.Name("go.micro.srv.greeter"),
)
// optionally setup command line usage
service.Init()
// Register Handlers
hello.RegisterSayHandler(service.Server(), new(Say))
// Run server
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
有关服务间通信使用的proto结构体
syntax = "proto3";
package go.micro.srv.greeter;
service Say {
rpc Hello(Request) returns (Response) {}
}
message Request {
string name = 1;
}
message Response {
string msg = 1;
}
启动服务的时候,通过日志输出,可以大致的了解启动过程
2021-09-27 15:47:40 file=v3@v3.5.2-0.20210630062103-c13bb07171bc/service.go:199 level=info Starting [service] go.micro.srv.greeter
2021-09-27 15:47:40 file=server/rpc_server.go:820 level=info Transport [http] Listening on [::]:64873
2021-09-27 15:47:40 file=server/rpc_server.go:840 level=info Broker [http] Connected to 127.0.0.1:64874
2021-09-27 15:47:40 file=server/rpc_server.go:654 level=info Registry [mdns] Registering node: go.micro.srv.greeter-042f3737-1410-4a86-9fe5-8f23fc5cc05b
service.Init()做初始化的时候,会把所有的无提供Options的服务进行默认的处理
service.Run()启动service,
if logger.V(logger.InfoLevel, logger.DefaultLogger) {
logger.Infof("Starting [service] %s", s.Name())
}
然后启动server.默认的server是newRpcServer.rpcServer启动的过程如下:
- 在transport上进行监听
- swap address
- 连接broker
- 注册前检查
- 然后就是for循环监听conn上的请求,并处理
ts.Accept方法我们在之前的内容中说过,在我们的server劫持到请求后,组装新的socket让rpcServer.ServeConn方法来进行处理,在服务移除的时候,会关闭transport的监听
func (s *rpcServer) Start() error {
s.RLock()
if s.started {
s.RUnlock()
return nil
}
s.RUnlock()
config := s.Options()
// start listening on the transport
ts, err := config.Transport.Listen(config.Address)
if err != nil {
return err
}
if logger.V(logger.InfoLevel, logger.DefaultLogger) {
logger.Infof("Transport [%s] Listening on %s", config.Transport.String(), ts.Addr())
}
// swap address
s.Lock()
addr := s.opts.Address
s.opts.Address = ts.Addr()
s.Unlock()
bname := config.Broker.String()
// connect to the broker
if err := config.Broker.Connect(); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Broker [%s] connect error: %v", bname, err)
}
return err
}
if logger.V(logger.InfoLevel, logger.DefaultLogger) {
logger.Infof("Broker [%s] Connected to %s", bname, config.Broker.Address())
}
// use RegisterCheck func before register
if err = s.opts.RegisterCheck(s.opts.Context); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Server %s-%s register check error: %s", config.Name, config.Id, err)
}
} else {
// announce self to the world
if err = s.Register(); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Server %s-%s register error: %s", config.Name, config.Id, err)
}
}
}
exit := make(chan bool)
go func() {
for {
// listen for connections
err := ts.Accept(s.ServeConn)
// TODO: listen for messages
// msg := broker.Exchange(service).Consume()
select {
// check if we're supposed to exit
case <-exit:
return
// check the error and backoff
default:
if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Accept error: %v", err)
}
time.Sleep(time.Second)
continue
}
}
// no error just exit
return
}
}()
go func() {
t := new(time.Ticker)
// only process if it exists
if s.opts.RegisterInterval > time.Duration(0) {
// new ticker
t = time.NewTicker(s.opts.RegisterInterval)
}
// return error chan
var ch chan error
Loop:
for {
select {
// register self on interval
case <-t.C:
s.RLock()
registered := s.registered
s.RUnlock()
rerr := s.opts.RegisterCheck(s.opts.Context)
if rerr != nil && registered {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Server %s-%s register check error: %s, deregister it", config.Name, config.Id, err)
}
// deregister self in case of error
if err := s.Deregister(); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Server %s-%s deregister error: %s", config.Name, config.Id, err)
}
}
} else if rerr != nil && !registered {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Server %s-%s register check error: %s", config.Name, config.Id, err)
}
continue
}
if err := s.Register(); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Server %s-%s register error: %s", config.Name, config.Id, err)
}
}
// wait for exit
case ch = <-s.exit:
t.Stop()
close(exit)
break Loop
}
}
s.RLock()
registered := s.registered
s.RUnlock()
if registered {
// deregister self
if err := s.Deregister(); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Server %s-%s deregister error: %s", config.Name, config.Id, err)
}
}
}
s.Lock()
swg := s.wg
s.Unlock()
// wait for requests to finish
if swg != nil {
swg.Wait()
}
// close transport listener
ch <- ts.Close()
if logger.V(logger.InfoLevel, logger.DefaultLogger) {
logger.Infof("Broker [%s] Disconnected from %s", bname, config.Broker.Address())
}
// disconnect the broker
if err := config.Broker.Disconnect(); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("Broker [%s] Disconnect error: %v", bname, err)
}
}
// swap back address
s.Lock()
s.opts.Address = addr
s.Unlock()
}()
// mark the server as started
s.Lock()
s.started = true
s.Unlock()
return nil
}
在ServeConn中涉及的sock参数的接收和发送都是我们httpTransportSocket中定义的动作,具体操作,可以详细的看看源码。
然后实现一个简单的客户端请求
package main
import (
"context"
"fmt"
hello "github.com/asim/go-micro/examples/v3/greeter/srv/proto/hello"
"github.com/asim/go-micro/v3"
)
func main() {
// create a new service
service := micro.NewService()
// parse command line flags
service.Init()
// Use the generated client stub
cl := hello.NewSayService("go.micro.srv.greeter", service.Client())
// Make request
rsp, err := cl.Hello(context.Background(), &hello.Request{
Name: "John",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(rsp.Msg)
}
在客户端调用服务端方法的时候,这就涉及到client方法调用流程,默认的Client实现是rpcClient,最主要的部分是它的call方法
func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, resp interface{}, opts CallOptions) error {
address := node.Address
msg := &transport.Message{
Header: make(map[string]string),
}
md, ok := metadata.FromContext(ctx)
if ok {
for k, v := range md {
// don't copy Micro-Topic header, that used for pub/sub
// this fix case then client uses the same context that received in subscriber
if k == "Micro-Topic" {
continue
}
msg.Header[k] = v
}
}
// set timeout in nanoseconds
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
// set the content type for the request
msg.Header["Content-Type"] = req.ContentType()
// set the accept header
msg.Header["Accept"] = req.ContentType()
// setup old protocol
cf := setupProtocol(msg, node)
// no codec specified
if cf == nil {
var err error
cf, err = r.newCodec(req.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
}
dOpts := []transport.DialOption{
transport.WithStream(),
}
if opts.DialTimeout >= 0 {
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
}
c, err := r.pool.Get(address, dOpts...)
if err != nil {
return errors.InternalServerError("go.micro.client", "connection error: %v", err)
}
seq := atomic.AddUint64(&r.seq, 1) - 1
codec := newRpcCodec(msg, c, cf, "")
rsp := &rpcResponse{
socket: c,
codec: codec,
}
stream := &rpcStream{
id: fmt.Sprintf("%v", seq),
context: ctx,
request: req,
response: rsp,
codec: codec,
closed: make(chan bool),
release: func(err error) { r.pool.Release(c, err) },
sendEOS: false,
}
// close the stream on exiting this function
defer stream.Close()
// wait for error response
ch := make(chan error, 1)
go func() {
defer func() {
if r := recover(); r != nil {
ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r)
}
}()
// send request
if err := stream.Send(req.Body()); err != nil {
ch <- err
return
}
// recv request
if err := stream.Recv(resp); err != nil {
ch <- err
return
}
// success
ch <- nil
}()
var grr error
select {
case err := <-ch:
return err
case <-ctx.Done():
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
}
// set the stream error
if grr != nil {
stream.Lock()
stream.err = grr
stream.Unlock()
return grr
}
return nil
}
该方法首先组装transport.Message消息体,该消息体包含两部分,一部分为Header,一部分为Body.
组装完成后进行消息编码,然后通过rpcStream方式进行发送请求和接收响应。
以rpcStream的Recv方法为例,我们可以发现,其读取请求的消息是通过codec,
func (r *rpcStream) Recv(msg interface{}) error {
r.Lock()
defer r.Unlock()
if r.isClosed() {
r.err = errShutdown
return errShutdown
}
var resp codec.Message
r.Unlock()
err := r.codec.ReadHeader(&resp, codec.Response)
r.Lock()
if err != nil {
if err == io.EOF && !r.isClosed() {
r.err = io.ErrUnexpectedEOF
return io.ErrUnexpectedEOF
}
r.err = err
return err
}
switch {
case len(resp.Error) > 0:
// We've got an error response. Give this to the request;
// any subsequent requests will get the ReadResponseBody
// error if there is one.
if resp.Error != lastStreamResponseError {
r.err = serverError(resp.Error)
} else {
r.err = io.EOF
}
r.Unlock()
err = r.codec.ReadBody(nil)
r.Lock()
if err != nil {
r.err = err
}
default:
r.Unlock()
err = r.codec.ReadBody(msg)
r.Lock()
if err != nil {
r.err = err
}
}
return r.err
}
codec读取消息其实就是调用client的Recv,这个client就是我们开头提到的httpTransportClient.
func (c *rpcCodec) ReadHeader(m *codec.Message, r codec.MessageType) error {
var tm transport.Message
// read message from transport
if err := c.client.Recv(&tm); err != nil {
return errors.InternalServerError("go.micro.client.transport", err.Error())
}
c.buf.rbuf.Reset()
c.buf.rbuf.Write(tm.Body)
// set headers from transport
m.Header = tm.Header
// read header
err := c.codec.ReadHeader(m, r)
// get headers
getHeaders(m)
// return header error
if err != nil {
return errors.InternalServerError("go.micro.client.codec", err.Error())
}
return nil
}