Golang 网络编程(二)

简介: Golang 网络编程(二)

HttpServer源码阅读#


注册路由#


直观上看注册路由这一步,就是它要做的就是将在路由器url pattern和开发者提供的func关联起来。 很容易想到,它里面很可能是通过map实现的。


func main() {
  // 创建路由器
  // 为路由器绑定路由规则
  mux := http.NewServeMux()
  mux.HandleFunc("/login", doLogin)
  ...
}
func doLogin(writer http.ResponseWriter,req *http.Request){
  _, err := writer.Write([]byte("do login"))
  if err != nil {
    fmt.Printf("error : %v", err)
    return
  }
}


姑且将ServeMux当作是路由器。我们使用http包下的 NewServerMux 函数创建一个新的路由器对象,进而使用它的HandleFunc(pattern,func)函数完成路由的注册。

跟进NewServerMux函数,可以看到,它通过new函数返回给我们一个ServeMux结构体。


func NewServeMux() *ServeMux {
  return new(ServeMux) 
}


这个ServeMux结构体长下面这样:在这个ServeMux结构体中我们就看到了这个维护pattern和func的map


type ServeMux struct {
  mu    sync.RWMutex 
  m     map[string]muxEntry
  hosts bool // whether any patterns contain hostnames
}


这个muxEntry长下面这样:


type muxEntry struct {
  h       Handler
  pattern string
}
type Handler interface {
  ServeHTTP(ResponseWriter, *Request)
}



看到这里问题就来了,上面我们手动注册进路由器中的仅仅是一个有规定参数的方法,到这里怎么成了一个Handle了?我们也没有说去手动的实现Handler这个接口,也没有重写ServeHTTP函数啊, 在golang中实现一个接口不得像下面这样搞吗?**


type Handle interface {
  Serve(string, int, string)
}
type HandleImpl struct {
}
func (h HandleImpl)Serve(string, int, string){
}


带着这个疑问看下面的方法:


// 由于函数是一等公民,故我们将doLogin函数同普通变量一样当做入参传递进去。
  mux.HandleFunc("/login", doLogin)
  func doLogin(writer http.ResponseWriter,req *http.Request){
    ...
  }


跟进去看 HandleFunc 函数的实现:


首先:HandleFunc函数的第二个参数是接收的函数的类型和doLogin函数的类型是一致的,所以doLogin能正常的传递进HandleFunc中。


其次:我们的关注点应该是下面的HandlerFunc(handler)


// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  if handler == nil {
    panic("http: nil handler")
  }
  mux.Handle(pattern, HandlerFunc(handler))
}


跟进这个HandlerFunc(handler) 看到下图,真相就大白于天下了。golang以一种优雅的方式悄无声息的为我们完成了一次适配。这么看来上面的HandlerFunc(handler)并不是函数的调用,而是doLogin转换成自定义类型。这个自定义类型去实现了Handle接口(因为它重写了ServeHTTP函数)以闭包的形式完美的将我们的doLogin适配成了Handle类型。



在往下看Handle方法:

第一:将pattern和handler注册进map中

第二:为了保证整个过程的并发安全,使用锁保护整个过程。


// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
  mux.mu.Lock()
  defer mux.mu.Unlock()
  if pattern == "" {
    panic("http: invalid pattern")
  }
  if handler == nil {
    panic("http: nil handler")
  }
  if _, exist := mux.m[pattern]; exist {
    panic("http: multiple registrations for " + pattern)
  }
  if mux.m == nil {
    mux.m = make(map[string]muxEntry)
  }
  mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
  if pattern[0] != '/' {
    mux.hosts = true
  }


启动服务#


概览图:



和java对比着看,在java一组复杂的逻辑会被封装成一个class。在golang中对应的就是一组复杂的逻辑会被封装成一个结构体。


对应HttpServer肯定也是这样,http服务器在golang的实现中有自己的结构体。它就是http包下的Server。


它有一系列描述性属性。如监听的地址、写超时时间、路由器。


server := &http.Server{
    Addr:         ":8081",
    WriteTimeout: time.Second * 2,
    Handler:      mux,
  }
  log.Fatal(server.ListenAndServe())


我们看它启动服务的函数:server.ListenAndServe()

实现的逻辑是使用net包下的Listen函数,获取给定地址上的tcp连接。

再将这个tcp连接封装进 tcpKeepAliveListenner 结构体中。

在将这个tcpKeepAliveListenner丢进Server的Serve函数中处理


// ListenAndServe 会监听开发者给定网络地址上的tcp连接,当有请求到来时,会调用Serve函数去处理这个连接。
// 它接收到所有连接都使用 TCP keep-alives相关的配置
// 
// 如果构造Server时没有指定Addr,他就会使用默认值: “:http”
// 
// 当Server ShutDown或者是Close,ListenAndServe总是会返回一个非nil的error。
// 返回的这个Error是 ErrServerClosed
func (srv *Server) ListenAndServe() error {
  if srv.shuttingDown() {
    return ErrServerClosed
  }
  addr := srv.Addr
  if addr == "" {
    addr = ":http"
  }
  // 底层借助于tcp实现
  ln, err := net.Listen("tcp", addr)
  if err != nil {
    return err
  }
  return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
// tcpKeepAliveListener会为TCP设置一个keep-alive 超时时长。
// 它通常被 ListenAndServe 和 ListenAndServeTLS使用。
// 它保证了已经dead的TCP最终都会消失。
type tcpKeepAliveListener struct {
  *net.TCPListener
}


接着去看看Serve方法,上一个函数中获取到了一个基于tcp的Listener,从这个Listener中可以不断的获取出新的连接,下面的方法中使用无限for循环完成这件事。conn获取到后将连接封装进httpConn,为了保证不阻塞下一个连接到到来,开启新的goroutine处理这个http连接。


func (srv *Server) Serve(l net.Listener) error {
  // 如果有一个包裹了 srv 和 listener 的钩子函数,就执行它
  if fn := testHookServerServe; fn != nil {
    fn(srv, l) // call hook with unwrapped listener
  }
  // 将tcp的Listener封装进onceCloseListener,保证连接不会被关闭多次。
  l = &onceCloseListener{Listener: l}
  defer l.Close()
  // http2相关的配置
  if err := srv.setupHTTP2_Serve(); err != nil {
    return err
  }
  if !srv.trackListener(&l, true) {
    return ErrServerClosed
  }
  defer srv.trackListener(&l, false)
  // 如果没有接收到请求睡眠多久
  var tempDelay time.Duration     // how long to sleep on accept failure
  baseCtx := context.Background() // base is always background, per Issue 16220
  ctx := context.WithValue(baseCtx, ServerContextKey, srv)
  // 开启无限循环,尝试从Listenner中获取连接。
  for {
    rw, e := l.Accept()
    // accpet过程中发生错屋
    if e != nil {
      select {
        // 如果从server的doneChan中可以获取内容,返回Server关闭了
      case <-srv.getDoneChan():
        return ErrServerClosed
      default:
      }
      // 如果发生了 net.Error 并且是临时的错误就睡5毫秒,再发生错误睡眠的时间*2,上线是1s
      if ne, ok := e.(net.Error); ok && ne.Temporary() {
        if tempDelay == 0 {
          tempDelay = 5 * time.Millisecond
        } else {
          tempDelay *= 2
        }
        if max := 1 * time.Second; tempDelay > max {
          tempDelay = max
        }
        srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
        time.Sleep(tempDelay)
        continue
      }
      return e
    }
    // 如果没有发生错误,清空睡眠的时间
    tempDelay = 0
    // 将接收到连接封装进httpConn
    c := srv.newConn(rw)
    c.setState(c.rwc, StateNew) // before Serve can return
    // 开启一条新的协程处理这个连接
    go c.serve(ctx)
  }
}


处理请求#


c.serve(ctx)中就会去解析http相关的报文信息~,将http报文解析进Request结构体中。


部分代码如下:


// 将 server 包裹为 serverHandler 的实例,执行它的 ServeHTTP 方法,处理请求,返回响应。
    // serverHandler 委托给 server 的 Handler 或者 DefaultServeMux(默认路由器)
    // 来处理 "OPTIONS *" 请求。
    serverHandler{c.server}.ServeHTTP(w, w.req)


// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
  srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  // 如果没有定义Handler就使用默认的
  handler := sh.srv.Handler
  if handler == nil {
    handler = DefaultServeMux
  }
  if req.RequestURI == "*" && req.Method == "OPTIONS" {
    handler = globalOptionsHandler{}
  }
  // 处理请求,返回响应。
  handler.ServeHTTP(rw, req)
}



可以看到,req中包含了我们前面说的pattern,叫做RequestUri,有了它下一步就知道该回调ServeMux中的哪一个函数。


相关文章
|
1月前
|
Go
golang力扣leetcode 2039.网络空闲的时刻
golang力扣leetcode 2039.网络空闲的时刻
16 0
|
7月前
|
监控 网络协议 Go
Golang抓包:实现网络数据包捕获与分析
Golang抓包:实现网络数据包捕获与分析
|
2天前
|
Go
golang读取网络字节并解压zip
golang读取网络字节并解压zip
3 0
|
1月前
|
存储 网络协议 Go
Golang网络聊天室案例
Golang网络聊天室案例
43 2
Golang网络聊天室案例
|
安全 Java Go
Golang出现泛型后,Gin怎么封装网络请求处理
Go 1.18后出现泛型,小白怎么使用Gin框架怎么根据泛型封装客户端请求,
483 0
|
网络协议 前端开发 Go
Golang 网络编程(三)
Golang 网络编程(三)
268 0
|
XML 存储 JSON
Golang 网络编程(一)
Golang 网络编程(一)
149 0
|
运维 监控 网络协议
golang 服务诡异499、504网络故障排查
事故经过 排查 总结 事故经过 11-01 12:00 中午午饭期间,手机突然收到业务网关非200异常报警,平时也会有一些少量499或者网络抖动问题触发报警,但是很快就会恢复(目前配置的报警阈值是5%,阈值跟当时的采样窗口qps有直接关系)。
5158 0
|
负载均衡 网络协议 前端开发
【开源】gnet: 一个轻量级且高性能的 Golang 网络库
gnet 是一个基于 Event-Loop 事件驱动的高性能和轻量级网络库。这个库直接使用 epoll 和 kqueue 系统调用而非标准 Golang 网络包:net 来构建网络应用,它的工作原理类似于两个开源的网络库:libuv 和 libevent。
3203 0