面试官:net/http库知道吗?能说说优缺点吗?

小白debug 2023-01-11 08:00

前言

哈喽,大家后,我是asong;这几天看了一下Go语言标准库net/http的源码,所以就来分享一下我的学习心得;为什么会突然想看http标准库呢?因为在面试的时候面试官问我你知道Go语言的net/http库吗?他有什么有缺点吗?因为我没有看过这部分源码,所以一首凉凉送给我;

废话不多说,接下来请跟着我的脚步我们一起探索net/http

本文代码基于:Go1.19.3

net/http库的一个小demo

服务端:

import (
 "fmt"
 "net/http"
)

func getProfile(w http.ResponseWriter, r *http.Request) {
 fmt.Fprintln(w, "asong")
}

func main() {
 http.HandleFunc("/profile", getProfile)
 err := http.ListenAndServe(":8080"nil)
 if err != nil {
  fmt.Printf("http server failed, err: %v\n", err)
  return
 }
}

本地启动一个server端监听8080端口,并且提供路由/profile获取个人信息;

客户端:

import (
 "fmt"
 "io/ioutil"
 "net/http"
)

func main() {
 resp, err := http.DefaultClient.Get("http://127.0.0.1:8080/profile")
 if err != nil {
  fmt.Printf("get failed, err:%v\n", err)
  return
 }
 defer resp.Body.Close()
 body, err := ioutil.ReadAll(resp.Body)
 if err != nil {
  fmt.Printf("read from resp.body failed, err:%v\n", err)
  return
 }
 fmt.Println(string(body))
}

通过这样一个简单的例子,我们可以知道客户端我们主要使用http.Client{},服务端我们主要使用http.ListenAndServehttp.HandleFunc,所以我们就可以从这两个包入手来分别看一看客户端和服务端的代码具体是怎么封装;

客户端实现

客户端级别最高的抽象是net/http.Client{},具体结构如下:

type Client struct {
    Transport RoundTripper
    CheckRedirect func(req *Request, via []*Request) error
    Jar CookieJar
    Timeout time.Duration
}
  • Transport:其类型是RoundTripper,RoundTrip代表一个本地事务,RoundTripper接口的实现主要有三个:Transport、http2Transport、fileTransport,其目的是支持更好的扩展性;
  • CheckRedirect:用来做重定向
  • Jar:其类型是CookieJar,用来做cookie管理,CookieJar接口的实现Jar结构体在源码包net/http/cookiejar/jar.go

客户端可以直接通过net/http.DefaultClient发起HTTP请求,也可以自己构建新的net/http.Client实现自定义的HTTP事务,多数情况下我们使用默认的客户端发出的请求就可以满足需求;

我们画一个UML图,如下所示:

image-20221204160448320

了解HTTP客户端的基本结构,我们接下来就开始分析客户端的基本实现;

构建Request

net/http包的Request结构体封装好了HTTP请求所需的必要信息:

type Request struct {
 Method string
 URL *url.URL
 Proto      string // "HTTP/1.0"
 ProtoMajor int    // 1
 ProtoMinor int    // 0
 Header Header
 Body io.ReadCloser
 GetBody func() (io.ReadCloser, error)
 ContentLength int64
 removed as necessary when sending and
 TransferEncoding []string
 Close bool
 Host string
 Form url.Values
 PostForm url.Values
 MultipartForm *multipart.Form
 Trailer Header
 RemoteAddr string
 RequestURI string
 TLS *tls.ConnectionState
 Cancel <-chan struct{}
 Response *Response
 ctx context.Context
}

其中包含了HTTP请求的方法、URL、协议版本、协议头以及请求体等字段,还包括了指向响应的引用:Response;其提供了NewRequest()、NewRequestWithContext()两个方法用来构建请求,这个方法可以校验HTTP请求的字段并根据输入的参数拼装成新的请求结构体,NewRequest()方法内部也是调用的NewRequestWithContext,区别就是是否使用context来做goroutine上下文传递;接下来我们看一下NewRequestWithContext方法的具体实现:

func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
    // 默认使用GET方法
 if method == "" {
  method = "GET"
 }
    // 校验方法是否有效,也就是是否是GET、POST、PUT等
 if !validMethod(method) {
  return nil, fmt.Errorf("net/http: invalid method %q", method)
 }
    // ctx必须要传递,NewRequest方法调用时会传递context.Background()
 if ctx == nil {
  return nil, errors.New("net/http: nil Context")
 }
    // 解析URL,解析Scheme、Host、Path等信息
 u, err := urlpkg.Parse(url)
 if err != nil {
  return nil, err
 }
    // body在下面会根据其类型包装成io.ReadCloser类型
 rc, ok := body.(io.ReadCloser)
 if !ok && body != nil {
  rc = io.NopCloser(body)
 }
 // The host's colon:port should be normalized. See Issue 14836.
 u.Host = removeEmptyPort(u.Host)
 req := &Request{
  ctx:        ctx,
  Method:     method,
  URL:        u,
  Proto:      "HTTP/1.1",
  ProtoMajor: 1,
  ProtoMinor: 1,
  Header:     make(Header),
  Body:       rc,
  Host:       u.Host,
 }
 if body != nil {
  switch v := body.(type) {
  case *bytes.Buffer:
   req.ContentLength = int64(v.Len())
   buf := v.Bytes()
   req.GetBody = func() (io.ReadCloser, error) {
    r := bytes.NewReader(buf)
    return io.NopCloser(r), nil
   }
  case *bytes.Reader:
   req.ContentLength = int64(v.Len())
   snapshot := *v
   req.GetBody = func() (io.ReadCloser, error) {
    r := snapshot
    return io.NopCloser(&r), nil
   }
  case *strings.Reader:
   req.ContentLength = int64(v.Len())
   snapshot := *v
   req.GetBody = func() (io.ReadCloser, error) {
    r := snapshot
    return io.NopCloser(&r), nil
   }
  default:
  if req.GetBody != nil && req.ContentLength == 0 {
   req.Body = NoBody
   req.GetBody = func() (io.ReadCloser, error) { return NoBody, nil }
  }
 }

 return req, nil
}

整个构建请求过程中只有处理body的时候稍有一些复杂,我们需要根据body的类型使用不同的方法将其包装成io.ReadCloser类型;

启动事务

构建HTTP请求后,接下来我们需要开启HTTP事务进行请求并且等待远程响应,我们以net/http.Client.Do()方法为例子,我们看一下它的调用链路:

  • net/http.Client.Do()
  • net/http.Client.do()
  • net/http.Client.send()
  • net/http.Send()
  • net/http.Transport.RoundTrip()

RoundTrip()是RoundTripper类型中的一个的方法,net/http.Transport是其中的一个实现,在net/http/transport.go文件中我们可以找到这个方法:

// roundTrip implements a RoundTripper over HTTP.
func (t *Transport) roundTrip(req *Request) (*Response, error) {
 t.nextProtoOnce.Do(t.onceSetNextProtoDefaults)
 ctx := req.Context()
 trace := httptrace.ContextClientTrace(ctx)
    // 省略前置检查部分
    .....
 for {
        // 用来检测ctx退出信号
  select {
  case <-ctx.Done():
   req.closeBody()
   return nil, ctx.Err()
  default:
  }

  // 获取连接,这块就是我们要看的重点,Go语言通过连接池对资源进行了复用;
  pconn, err := t.getConn(treq, cm)
  if err != nil {
   t.setReqCanceler(cancelKey, nil)
   req.closeBody()
   return nil, err
  }

  var resp *Response
  if pconn.alt != nil {
   // HTTP/2 path.
   t.setReqCanceler(cancelKey, nil// not cancelable with CancelRequest
   resp, err = pconn.alt.RoundTrip(req)
  } else {
            // 开始处理响应
   resp, err = pconn.roundTrip(treq)
  }
  if err == nil {
   resp.Request = origReq
   return resp, nil
  }

  // Rewind the body if we're able to.
  req, err = rewindBody(req)
  if err != nil {
   return nil, err
  }
 }
}

代码一大堆,我们只要重点看两部分即可:

  • net/http.Transport.getConn()获取连接
  • net/http.persistConn.roundTrip()处理写入HTTP请求并在select中等待响应的返回;

获取连接

func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {
 req := treq.Request
 trace := treq.trace
 ctx := req.Context()
 if trace != nil && trace.GetConn != nil {
  trace.GetConn(cm.addr())
 }

 w := &wantConn{
  cm:         cm,
  key:        cm.key(),
  ctx:        ctx,
  ready:      make(chan struct{}, 1),
  beforeDial: testHookPrePendingDial,
  afterDial:  testHookPostPendingDial,
 }
 defer func() {
  if err != nil {
   w.cancel(t, err)
  }
 }()

 // 在队列中有闲置连接,直接返回
 if delivered := t.queueForIdleConn(w); delivered {
  pc := w.pc
  return pc, nil
 }

 cancelc := make(chan error, 1)
 t.setReqCanceler(treq.cancelKey, func(err error) { cancelc <- err })

 // 放到队列中等待建立新的连接
 t.queueForDial(w)

 // 阻塞等待连接
 select {
 case <-w.ready:
  return w.pc, w.err
 case <-req.Cancel:
  return nil, errRequestCanceledConn
 case <-req.Context().Done():
  return nil, req.Context().Err()
 case err := <-cancelc:
  if err == errRequestCanceled {
   err = errRequestCanceledConn
  }
  return nil, err
 }
}

因为连接的建议会消耗比较多的时间,带来较大的开下,所以Go语言使用了连接池对资源进行分配和复用,先调用net/http.Transport.queueForIdleConn()获取等待闲置的连接,如果没有获取到在调用net/http.Transport.queueForDial在队列中等待建立新的连接,通过select监听连接是否建立完毕,超时未获取到连接会上剖错误,我们继续在queueForDial追踪TCP连接的建立:

func (t *Transport) queueForDial(w *wantConn) {
 w.beforeDial()
 if t.MaxConnsPerHost <= 0 {
  go t.dialConnFor(w)
  return
 }

 t.connsPerHostMu.Lock()
 defer t.connsPerHostMu.Unlock()

 if n := t.connsPerHost[w.key]; n < t.MaxConnsPerHost {
  if t.connsPerHost == nil {
   t.connsPerHost = make(map[connectMethodKey]int)
  }
  t.connsPerHost[w.key] = n + 1
  go t.dialConnFor(w)
  return
 }
    ....
}

我们会启动一个goroutine做tcp的建连,最终调用dialConn方法,在这个方法内做持久化连接,调用net库的dial方法进行TCP连接:

func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *persistConn, err error) {
 pconn = &persistConn{
  t:             t,
  cacheKey:      cm.key(),
  reqch:         make(chan requestAndChan, 1),
  writech:       make(chan writeRequest, 1),
  closech:       make(chan struct{}),
  writeErrCh:    make(chan error, 1),
  writeLoopDone: make(chan struct{}),
 }

 conn, err := t.dial(ctx, "tcp", cm.addr())
 if err != nil {
  return nil, err
 }
 pconn.conn = conn

 pconn.br = bufio.NewReaderSize(pconn, t.readBufferSize())
 pconn.bw = bufio.NewWriterSize(persistConnWriter{pconn}, t.writeBufferSize())

 go pconn.readLoop()
 go pconn.writeLoop()
 return pconn, nil
}

在连接建立后,代码中我们我们还看到分别启动了两个goroutine,readLoop用于从tcp连接中读取数据,writeLoop用于从tcp连接中写入数据;

我们看一下writeLoop方法:

func (pc *persistConn) writeLoop() {
 defer close(pc.writeLoopDone)
 for {
  select {
  case wr := <-pc.writech:
   startBytesWritten := pc.nwrite
   err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra, pc.waitForContinue(wr.continueCh))
   if bre, ok := err.(requestBodyReadError); ok {
    err = bre.error
    wr.req.setError(err)
   }
   if err == nil {
    err = pc.bw.Flush()
   }
   if err != nil {
    if pc.nwrite == startBytesWritten {
     err = nothingWrittenError{err}
    }
   }
   pc.writeErrCh <- err // to the body reader, which might recycle us
   wr.ch <- err         // to the roundTrip function
   if err != nil {
    pc.close(err)
    return
   }
  case <-pc.closech:
   return
  }
 }
}

监听writech通道,所以的数据发送都是在这个循环中写入的;

net/http.Transport{}中提供了连接池配置参数,开发者可以自行定义:

type Transport struct {
 ......
    MaxIdleConns int
    MaxIdleConnsPerHost int
    MaxConnsPerHost int
    IdleConnTimeout time.Duration
    ......
}

处理HTTP请求

net/http.persistConn.roundTrip()会处理HTTP请求,我们看其具体实现:

func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
    ......
 startBytesWritten := pc.nwrite
 writeErrCh := make(chan error, 1)
    //writeLoop循环写入
 pc.writech <- writeRequest{req, writeErrCh, continueCh}
    
    // 等待响应数据
    resc := make(chan responseAndError)
    // readLoop循环等待响应
 pc.reqch <- requestAndChan{
  req:        req.Request,
  cancelKey:  req.cancelKey,
  ch:         resc,
  addedGzip:  requestedGzip,
  continueCh: continueCh,
  callerGone: gone,
 }
    
 for {
  select {
  case err := <-writeErrCh:
  case <-pcClosed:
   pcClosed = nil
   if canceled || pc.t.replaceReqCanceler(req.cancelKey, nil) {
    if debugRoundTrip {
     req.logf("closech recv: %T %#v", pc.closed, pc.closed)
    }
    return nil, pc.mapRoundTripError(req, startBytesWritten, pc.closed)
   }
  case <-respHeaderTimer:
   if debugRoundTrip {
    req.logf("timeout waiting for response headers.")
   }
   pc.close(errTimeout)
   return nil, errTimeout
  case re := <-resc:
   if (re.res == nil) == (re.err == nil) {
    panic(fmt.Sprintf("internal error: exactly one of res or err should be set; nil=%v", re.res == nil))
   }
   if debugRoundTrip {
    req.logf("resc recv: %p, %T/%#v", re.res, re.err, re.err)
   }
   if re.err != nil {
    return nil, pc.mapRoundTripError(req, startBytesWritten, re.err)
   }
   return re.res, nil
  case <-cancelChan:
   canceled = pc.t.cancelRequest(req.cancelKey, errRequestCanceled)
   cancelChan = nil
  case <-ctxDoneChan:
   canceled = pc.t.cancelRequest(req.cancelKey, req.Context().Err())
   cancelChan = nil
   ctxDoneChan = nil
  }
 }
}

我们重点关注这两个通道:

  • pc.writech :其类型是chan writeRequest ,writeLoop协程会循环写入数据,net/http.Request.write会根据net/http.Request结构中的字段按照HTTP协议组成TCP数据段,TCP协议栈会负责将HTTP请求中的内容发送到目标服务器上;
  • pc.reqch:其类型是chan requestAndChan,readLoop协程会循环读取响应数据并且调用net/http.ReadResponse进行协议解析,其中包含状态码、协议版本、请求头等内容;

小结

我们简单总结一下net/http库中HTTP客户端的实现:

  • net/http.Client是级别最高的抽象,其中transport用于开启HTTP事务,jar用于处理cookie;
  • net/http.Transport中主要逻辑两部分:
    • 从连接池中获取持久化连接
    • 使用持久化连接处理HTTP请求

net/http库中默认有一个DefaultClient可以直接使用,DefaultClient有对应DefaultTransport,可以满足我们大多数场景,如果需要使用自己管理HTTP客户端的头域、重定向等策略,那么可以自定义Client,如果需要管理代理、TLS配置、连接池、压缩等设置,可以自定义Transport;

因为HTTP协议的版本是不断变化的,所以为了可扩展性,transport是一个接口类型,具体的是实现是Transporthttp2TransportfileTransport,这样实现扩展性变高,值得我们学习;

HTTP在建立连接时会耗费大量的资源,需要开辟一个goroutine去创建TCP连接,连接建立后会在创建两个goroutine用于HTTP请求的写入和响应的解析,然后使用channel进行通信,所以要合理利用连接池,避免大量的TCP连接的建立可以优化性能;

服务端

我们可以用net/http库快速搭建HTTP服务,HTTP服务端主要包含两部分:

  • 注册处理器:net/http.HandleFunc函数用于注册处理器
  • 监听端口:net/http.ListenAndServe用于处理请求

注册处理器

直接调用net/http.HandleFunc可以注册路由和处理函数:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
 DefaultServeMux.HandleFunc(pattern, handler)
}

我们可以看到处理函数是一个统一的格式:

handler func(ResponseWriter, *Request)

默认调用HTTP服务起的DefaultServeMux处理请求,DefaultServeMux本质是ServeMux

type ServeMux struct {
 mu    sync.RWMutex  // 读写锁,保证并发安全,注册处理器时会加写锁做保护
 m     map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
 es    []muxEntry // slice of entries sorted from longest to shortest.
 hosts bool       // whether any patterns contain hostnames
}
  • mu:需要加读写锁保证并发安全,注册处理器时会加写锁保证写map的数据正确性,这个map就是pattern和handler;
  • m:存储路由规则,key就是pattern,value是muEntry实体,muEntry实体中包含:pattern和handler
  • es:存储的也是muxEntry实体,因为我们使用map存储路由和handler的对应关系,所以只能索引静态路由,并不支持[path_param],所以这块的作用是当在map中没有找到匹配的路由时,会遍历这个切片进行前缀匹配,这个切片按照路由长度进行排序;
  • hosts:这个也是用来应对特殊case,如果我们注册的路由没有以/开始,那么就认为我们注册的路由包含host,所以路由匹配时需要加上host;

我看看一下路由注册函数:

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}
    // map存储路由和处理函数的映射
 mux.m[pattern] = e
    // 如果路由最后加了`/`放入到切片后在路由匹配时做前缀匹配
 if pattern[len(pattern)-1] == '/' {
  mux.es = appendSorted(mux.es, e)
 }
 // 如果路由第一位不是/,则认为注册的路由加上了host,所以在路由匹配时使用host+path进行匹配;
 if pattern[0] != '/' {
  mux.hosts = true
 }
}

监听端口

net/http库提供了ListenAndServe()用来监听TCP连接并处理请求:

func ListenAndServe(addr string, handler Handler) error {
 server := &Server{Addr: addr, Handler: handler}
 return server.ListenAndServe()
}

在这里初始化Server结构,然后调用ListenAndServe

func (srv *Server) ListenAndServe() error {
 if srv.shuttingDown() {
  return ErrServerClosed
 }
 addr := srv.Addr
 if addr == "" {
  addr = ":http"
 }
    // 调用net进行tcp连接
 ln, err := net.Listen("tcp", addr)
 if err != nil {
  return err
 }
 return srv.Serve(ln)
}

我们调用net网络库进行tcp连接,这里包含了创建socket、bind绑定socket与地址,listen端口的操作,最后调用Serve方法循环等待客户端的请求:

func (srv *Server) Serve(l net.Listener) error {

 origListener := l
 l = &onceCloseListener{Listener: l}
 defer l.Close()

 if err := srv.setupHTTP2_Serve(); err != nil {
  return err
 }

 if !srv.trackListener(&l, true) {
  return ErrServerClosed
 }
 defer srv.trackListener(&l, false)

 baseCtx := context.Background()
 if srv.BaseContext != nil {
  baseCtx = srv.BaseContext(origListener)
  if baseCtx == nil {
   panic("BaseContext returned a nil context")
  }
 }

 var tempDelay time.Duration // how long to sleep on accept failure

 ctx := context.WithValue(baseCtx, ServerContextKey, srv)
 for {
        // 接收客户端请求
  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
        // 读取起一个goroutine处理客户端请求
  go c.serve(connCtx)
 }
}

从上述代码我们可以到每个HTTP请求服务端都会单独创建一个goroutine来处理请求,我们一下处理过程:

// 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())
 var inFlightResponse *response
 defer func() {
        // 添加recover函数防止panic引发主程序挂掉;
  if err := recover(); err != nil && err != ErrAbortHandler {
   const size = 64 << 10
   buf := make([]byte, size)
   buf = buf[:runtime.Stack(buf, false)]
   c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
  }
 }()


 // HTTP/1.x from here on.
 ctx, cancelCtx := context.WithCancel(ctx)
 c.cancelCtx = cancelCtx
 defer cancelCtx()

 c.r = &connReader{conn: c}
 c.bufr = newBufioReader(c.r)
 c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

 for {
        // 读取请求,从连接中获取HTTP请求并构建一个实现了`net/http.Conn.ResponseWriter`接口的变量`net/http.response`
  w, err := c.readRequest(ctx)
  if c.r.remain != c.server.initialReadLimitSize() {
   c.setState(c.rwc, StateActive, runHooks)
  }
  if err != nil {
  }
  // 处理请求
  serverHandler{c.server}.ServeHTTP(w, w.req)
 }
}

我们继续跟踪ServeHTTP方法,ServeMux是一个HTTP请求的多路复用器,在这里可以根据请求的URL匹配合适的处理器,我们看代码:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
 if r.RequestURI == "*" {
  if r.ProtoAtLeast(11) {
   w.Header().Set("Connection""close")
  }
  w.WriteHeader(StatusBadRequest)
  return
 }
    // 进行路由匹配,获取注册的处理函数
 h, _ := mux.Handler(r)
    // 这块就是执行我们注册的handler,也就是例子中的getProfile()
 h.ServeHTTP(w, r)
}

mux.Handler()中我们就看到了路由匹配的代码:

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
 mux.mu.RLock()
 defer mux.mu.RUnlock()

 // Host-specific pattern takes precedence over generic ones
 if mux.hosts {
  h, pattern = mux.match(host + path)
 }
 if h == nil {
  h, pattern = mux.match(path)
 }
 if h == nil {
  h, pattern = NotFoundHandler(), ""
 }
 return
}
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
 // 先从map中查找
 v, ok := mux.m[path]
 if ok {
        // 找打了返回注册的函数
  return v.h, v.pattern
 }

 // 从切片中进行前缀匹配
 for _, e := range mux.es {
  if strings.HasPrefix(path, e.pattern) {
   return e.h, e.pattern
  }
 }
 return nil""
}

小结

服务端的代码看主逻辑主要是看两部分,一个是注册处理器,标准库使用map进行存储,本质是一个静态索引,同时维护了一个切片,用来做前缀匹配,只要以/结尾的,都会在切片中存储;服务端监听端口本质也是使用net网络库进行TCP连接,然后监听对应的TCP连接,每一个HTTP请求都会开一个goroutine去处理请求,所以如果有海量请求,会在一瞬间创建大量的goroutine,这个可能是一个性能瓶颈点,所以小伙伴要注意下这块的性能问题;

总结

net/HTTP的总体代码行数是比较多的,我们只需要看主要逻辑是怎么实现的就可以了,别人问你原理能打出来个所以然就行,不必要扣细节,当出现问题或者想具体了解某部分协议的时候在细看源码对应部分即可;

像我们过了一遍源码后我们就知道当前net/http的一些优缺点,比如优点是HTTP客户端使用了连接池,避免频繁建立带来的大开销,缺点是HTTP服务端的路由只是一个静态索引匹配,对于动态路由匹配支持的不好,并且每一个请求都会创建一个gouroutine进行处理,海量请求到来时需要考虑这块的性能瓶颈;

net/http标准库可以让我们很快的就实现一个HTTP服务器,并且也有很多我们值得借鉴学习的地方,所以源码的学习还是很必要的;

好啦,今天的文章就到这里了,我是asong,我们下期见~


评论
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 116浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 103浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • 国产光耦合器正以其创新性和多样性引领行业发展。凭借强大的研发能力,国内制造商推出了适应汽车、电信等领域独特需求的专业化光耦合器,为各行业的技术进步提供了重要支持。本文将重点探讨国产光耦合器的技术创新与产品多样性,以及它们在推动产业升级中的重要作用。国产光耦合器创新的作用满足现代需求的创新模式新设计正在满足不断变化的市场需求。例如,高速光耦合器满足了电信和数据处理系统中快速信号传输的需求。同时,栅极驱动光耦合器支持电动汽车(EV)和工业电机驱动器等大功率应用中的精确高效控制。先进材料和设计将碳化硅
    克里雅半导体科技 2024-11-29 16:18 181浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 102浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 58浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 78浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 73浏览
  • 艾迈斯欧司朗全新“样片申请”小程序,逾160种LED、传感器、多芯片组合等产品样片一触即达。轻松3步完成申请,境内免费包邮到家!本期热荐性能显著提升的OSLON® Optimal,GF CSSRML.24ams OSRAM 基于最新芯片技术推出全新LED产品OSLON® Optimal系列,实现了显著的性能升级。该系列提供五种不同颜色的光源选项,包括Hyper Red(660 nm,PDN)、Red(640 nm)、Deep Blue(450 nm,PDN)、Far Red(730 nm)及Ho
    艾迈斯欧司朗 2024-11-29 16:55 175浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 125浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 87浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 111浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦