关于golang:来我们探究一下nethttp的代码流程

42次阅读

共计 7825 个字符,预计需要花费 20 分钟才能阅读完成。

[TOC]

这是我参加更文挑战的第 3 天,流动详情查看:更文挑战

探索一下 net/http 的代码流程

net/http 是什么?

是 GO 的其中一个规范库,用于 Web 利用的开发,应用这个库,能够让开发变得更加迅速和简便,且易于上手。

那么问题来了

应用库,的确不便,无脑调接口,拼拼凑凑能跑就行,管他效率性能,出了问题,删库跑路就行了。。。

理论真的是这个样子吗? 作为一个开发,肯定要想方法弄明确不分明的事件,要弄明确用到工具的原理,更须要清晰的晓得本人开发产品的运作原理,正所谓

知其然,而不知其所以然,欲摹写其情状,而心不能自喻,口不能自宣,笔不能自传。

咱们对于技术要有摸索精力,对代码要有敬畏之心,那明天咱们就来看看 net/http 的代码流程吧

应用框架 / 库,必要要承受其本身的一套约定和模式,咱们必须要理解和相熟这些约定和模式的用法,否则就会陷入用错了都不晓得的地步。

在 GOLANG 中,net/http的组成部分有 客户端 服务端

库中的构造和函数有的只反对客户端和服务器这两者中的一个,有的同时反对客户端和服务器,用图谈话:

  • 只反对客户端的

Client,response

  • 只反对服务端的

ServerMux,Server,ResponseWriter,Handler 和 HandlerFunc

  • 客户端,服务端都反对的

Header,Request,Cookie

net/http 构建服务器也很简略,大体框架如下:

客户端 申请 服务器,服务器外面应用 net/http包,包中有多路复用器,和对应多路复用器的接口,服务器中的多个处理器解决不同的申请,最终须要落盘的数据即入库

万里长城第一步,咱们发车了

开始写一个简略的 Request

package main

import (
   "fmt"
   "net/http"
)

func main() {http.HandleFunc("/Hi", func(w http.ResponseWriter, r *http.Request) {
       // <h1></h1> 是 html 标签
      w.Write([]byte("<h1>Hi xiaomotong</h1>"))
   })
   
   // ListenAndServe 不写 ip 默认服务器地址是 127.0.0.1
   if err := http.ListenAndServe(":8888", nil); err != nil {fmt.Println("http server error:", err)
   }
    
}

运行服务器代码,在浏览器中输出 127.0.0.1:8888/Hi 或者localhost:8888/Hi,即可看到成果

创立一个 Go 写的服务器就是那么简略,只有调用 ListenAndServe 并传入网络地址,端口,解决申请的处理器(handler)即可。

留神:

  • 如果网络地址参数为空字符串,那么服务器默认应用 80 端口进行网络连接
  • 如果处理器参数为nil,那么服务器将应用默认的多路复用器DefaultServeMux

可是实际上 http 是如何建设起来的呢?一顿操作猛如虎,一问细节二百五

HTTP 的建设过程

HTTP 的建设流程都是通用的,因为他是标准协议。写 C/C++ 的时候,这些流程基本上本人都要去写一遍,然而写 GO 的时候,规范库外面曾经封装好了,因而才会有上述一个函数就能够写一个 web 服务器的状况

服务端波及的流程

  • socket 建设套接字
  • bind 绑定地址和端口
  • listen 设置最大监听数
  • accept 开始阻塞期待客户端的连贯
  • read 读取数据
  • write 回写数据
  • close 敞开

客户端波及的流程

  • socket 建设套接字
  • connect 连贯服务端
  • write 写数据
  • read 读取数据

那么数据在各个层级之间是如何走的呢

还是那个相熟的 7 层 OSI 模型,不过理论利用的话,咱们用TCP/IP 5 层模型

上述 TCP/IP 五层模型,可能会用到的协定大体列一下

  • 应用层:

    HTTP 协定,SMTP,SNMP,FTP,Telnet,SIP,SSH,NFS,RTSP

  • 传输层

比拟常见的协定是 TCP,UDP,SCTP,SPX,ATP 等

UDP 不牢靠, SCTP 有本人非凡的使用场景, 所以个别状况下 HTTP 是由 TCP 协定进行传输

不过企业应用的话,会将 UDP 革新成牢靠的传输,实际上是对规范 udp 上封装自定义的头,模仿 TCP 的牢靠传输,三次握手, 四次挥手就是在这里产生的

  • 网络层

IP 协定、ICMP,IGMP,IPX,BGP,OSPF,RIP,IGRP,EIGRP,ARP,RARP 协定,等等

  • 数据链路层

Ethernet,PPP,WiFi,802.11 等等

  • 物理层

SO2110,IEEE802 等等

晓得 HTTP 的通用流程,那么咱们来具体看看 net/http 规范库是如何实现这整个流程的,先从建设 socket 看起

net/http 建设 socket

还记得最下面说到的 request 小案例吗?咱们能够从这里开始动手

package main

import (
   "fmt"
   "net/http"
)

func main() {http.HandleFunc("/Hi", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("<h1>Hi xiaomotong</h1>"))
   })

   if err := http.ListenAndServe(":8888", nil); err != nil {fmt.Println("http server error:", err)
   }
}

http.HandleFunc(“/Hi”, func(w http.ResponseWriter, r *http.Request) {

  w.Write([]byte("<h1>Hi xiaomotong</h1>"))

})

HandleFunc 这一段是注册路由,这个路由的 handler 会默认放到到 DefaultServeMux

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler)
}

HandleFunc 实际上是调用了 ServeMux服务的HandleFunc

// 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))
}

ServeMux服务的 HandleFunc 调用了本人服务的Handle 实现

// 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)
   }
   e := muxEntry{h: handler, pattern: pattern}
   mux.m[pattern] = e
   if pattern[len(pattern)-1] == '/' {mux.es = appendSorted(mux.es, e)
   }

   if pattern[0] != '/' {mux.hosts = true}
}

看了理论的注册路由实现还是比较简单,咱们先不再深刻的网下看,要是感兴趣的能够接着这里往下追具体的数据结构

目前的注册路由的流程是:

  • http.HandleFunc ->
  • (mux *ServeMux) HandleFunc ->
  • (mux *ServeMux) Handle

net/http 监听端口 + 响应申请

那咱们在来看看方才 request 案例外面的监听地址和端口的代码是如何走的

if err := http.ListenAndServe(“:8888”, nil); err != nil {

  fmt.Println("http server error:", err)

}

通过下面的三个函数流程,曾经晓得注册路由是如何走的了,那么 ListenAndServe 这个函数的监听曾经 handler 解决数据后的响应是如何实现的呢?来咱们持续

ListenAndServe侦听 TCP 网络地址 addr,而后调用 handler 来解决传入连贯的申请,收的连贯配置为启用 TCP keep-alive,该参数通常为 nil,在这种状况下应用DefaultServeMux,下面提过一次,此处再次强调

// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}
   return server.ListenAndServe() // 调用 Server 服务的 ListenAndServe 函数 (srv *Server) ListenAndServe
}
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {if srv.shuttingDown() {return ErrServerClosed}
   addr := srv.Addr
   if addr == "" {addr = ":http"}
   ln, err := net.Listen("tcp", addr) // 理论是通过 net.Listen 进行监听地址和端口的
   if err != nil {return err}
   return srv.Serve(ln)
}
func Listen(network, address string) (Listener, error) {
   var lc ListenConfig
   return lc.Listen(context.Background(), network, address)
}
func (srv *Server) Serve(l net.Listener) error {
   if fn := testHookServerServe; fn != nil {fn(srv, l) // call hook with unwrapped listener // 回调函数的调用的地位
   }

 // ... 此处省略 15 行代码...
    
   var tempDelay time.Duration // how long to sleep on accept failure   // accept 阻塞失败睡眠的间隔时间
    
   ctx := context.WithValue(baseCtx, ServerContextKey, srv)
   for {
       // 开始 Accept 阻塞监听客户端的连贯
      rw, err := l.Accept()
      if err != nil {
         select {case <-srv.getDoneChan():
            return ErrServerClosed
         default:
         }
         if ne, ok := err.(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", err, tempDelay)
            time.Sleep(tempDelay)
            continue
         }
         return err
      }
      connCtx := ctx
      if cc := srv.ConnContext; cc != nil {connCtx = cc(connCtx, rw)
         if connCtx == nil {panic("ConnContext returned nil")
         }
      }
      tempDelay = 0
      c := srv.newConn(rw)
      c.setState(c.rwc, StateNew, runHooks) // before Serve can return
      go c.serve(connCtx)    // 此处开一个协程来解决具体的申请音讯
   }
}

此处通过 go c.serve(connCtx) 开启一个协程专门解决具体的申请音讯

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {c.remoteAddr = c.rwc.RemoteAddr().String()
   ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
   //  ... 此处省略局部代码
    // HTTP cannot have multiple simultaneous active requests.[*]
    // Until the server replies to this request, it can't read another,
    // so we might as well run the handler in this goroutine.
    // [*] Not strictly true: HTTP pipelining. We could let them all process
    // in parallel even if their responses need to be serialized.
    // But we're not going to implement HTTP pipelining because it
    // was never deployed in the wild and the answer is HTTP/2.
    //HTTP 不能同时有多个流动申请。[*], 直到服务器响应这个申请,它不能读取另一个
    serverHandler{c.server}.ServeHTTP(w, w.req)  // ServeHTTP 是重点
    w.cancelCtx()
    if c.hijacked() {return}
    w.finishRequest()
    //  ... 此处省略局部代码
}

此处 ServeHTTP 相当重要

// 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 := sh.srv.Handler
   if handler == nil {handler = DefaultServeMux}
   if req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}
   }
   handler.ServeHTTP(rw, req)
}
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
   if r.RequestURI == "*" {if r.ProtoAtLeast(1, 1) {w.Header().Set("Connection", "close")
      }
      w.WriteHeader(StatusBadRequest)
      return
   }
   h, _ := mux.Handler(r)
   h.ServeHTTP(w, r)
}

(sh serverHandler) ServeHTTP 调用 (mux *ServeMux) ServeHTTP,ServeHTTP 将申请发送给处理程序 h, _ := mux.Handler(r)

源码看到这里,对于 net/http 规范库 对于注册路由,监听服务端地址和端口的流程,大抵分明了吧

整个过程,net/http基本上是提供了 HTTP 流程的整套服务,能够说是十分的香了,整个过程基本上是这个样子的

  • net.Listen 做了初始化 套接字 socket,bind 绑定 ip 和端口,listen 设置最大监听数量的 操作
  • Accept 进行阻塞期待客户端的连贯
  • go c.serve(ctx) 启动 新的协程 来解决以后的申请. 同时 主协程 持续期待 其余客户端 的连贯, 进行高并发操作
  • mux.Handler获取注册的路由, 而后拿到这个路由的 handler 处理器,解决客户端的申请后,返回给客户端后果

对于底层是如何封包解包,字节是如何偏移的,ipv4,ipv6 如何去解决的,有趣味的敌人们能够顺着代码持续追,欢送多多沟通交流

好了,本次就到这里,下一次是 gin 的路由算法分享

技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。

我是 小魔童哪吒,欢送点赞关注珍藏,下次见~

正文完
 0