当前位置 博文首页 > lin_zone:fastHttp服务端处理请求的过程

    lin_zone:fastHttp服务端处理请求的过程

    作者:lin_zone 时间:2021-02-01 20:24

    fastHttp处理请求的过程
    1. Github 地址 https://github.com/valyala/fasthttp

    2. fastHttp 服务端的处理请求的过程

      1. 工作过程
        fastHttp服务端工作过程

      2. 主要代码

        1. 设置监听地址 server.go

          func (s *Server) ListenAndServe(addr string) error {
          	ln, err := net.Listen("tcp4", addr)
          	if err != nil {
          		return err
          	}
          	if tcpln, ok := ln.(*net.TCPListener); ok {
          		return s.Serve(tcpKeepaliveListener{
          			TCPListener:     tcpln,
          			keepalive:       s.TCPKeepalive,
          			keepalivePeriod: s.TCPKeepalivePeriod,
          		})
          	}
          	return s.Serve(ln)
          }
          
        2. 初始化协程池并启动,当接收到请求后,调 wp.Serve 交给协程池去处理请求 server.go

          func (s *Server) Serve(ln net.Listener) error {
          
           .....
          
           wp := &workerPool{
             WorkerFunc:      s.serveConn,
             MaxWorkersCount: maxWorkersCount,
             LogAllErrors:    s.LogAllErrors,
             Logger:          s.logger(),
             connState:       s.setState,
           }
           wp.Start()
          
           for {
             if c, err = acceptConn(s, ln, &lastPerIPErrorTime); err != nil {
               wp.Stop()
               return err
             }
             s.setState(c, StateNew)
             atomic.AddInt32(&s.open, 1)
             if !wp.Serve(c) {
               atomic.AddInt32(&s.open, -1)
               s.writeFastError(c, StatusServiceUnavailable,
                 "The connection cannot be served because Server.Concurrency limit exceeded")
               c.Close()
               s.setState(c, StateClosed)
             }
           }
           ......
          }
          
        3. 获取请求的句柄 server.go

          func acceptConn(s *Server, ln net.Listener, lastPerIPErrorTime *time.Time) (net.Conn, error) {
          	for {
          		c, err := ln.Accept()
          		......
          		return c, nil
          	}
          }
          
        4. 协程池处理请求 workerPool.go

          // 从协程池中获取某个协程对应的 句柄channel,然后将 3 中获取到的 请求句柄推入channel
          func (wp *workerPool) Serve(c net.Conn) bool {
          	ch := wp.getCh()
          	if ch == nil {
          		return false
          	}
          	ch.ch <- c
          	return true
          }
          
          // 这个是协程池工作最重要的部分,获取协程池中协程对应的channel
          func (wp *workerPool) getCh() *workerChan {
          	var ch *workerChan
          	createWorker := false
          
          	wp.lock.Lock()
          	ready := wp.ready
          	n := len(ready) - 1
            // 获取协程句柄channel失败,如果可以新建协程的话就进行新建,如果不可用新建的话,返回的句柄channel为nil,本次请求被拒绝服务
          	if n < 0 {
          		if wp.workersCount < wp.MaxWorkersCount {
          			createWorker = true
          			wp.workersCount++
          		}
          	} else {
              // 获取协程句柄channel 成功
          		ch = ready[n]
          		ready[n] = nil
          		wp.ready = ready[:n]
          	}
          	wp.lock.Unlock()
          
          	if ch == nil {
          		if !createWorker {
          			return nil
          		}
              // 新建协程句柄,且为之创建协程
          		vch := wp.workerChanPool.Get()
          		ch = vch.(*workerChan)
          		go func() {
          			wp.workerFunc(ch)
          			wp.workerChanPool.Put(vch)
          		}()
          	}
          	return ch
          }
          
          func (wp *workerPool) workerFunc(ch *workerChan) {
          	var c net.Conn
          	var err error
          	for c = range ch.ch {
              //协程的句柄channel 出现nil的时候 当前协程就退出了
          		if c == nil {
          			break
          		}
          
              // wp.WorkerFunc是在初始化协程池的时候注册的方法,是  s.serveConn
          		if err = wp.WorkerFunc(c); err != nil && err != errHijacked {
          		  ......
          			}
          		}
          		......
          		if !wp.release(ch) {
          			break
          		}
          	}
          	.....
            // 协程的句柄channel 出现nil的时候 当前协程就退出了,退出后将work协程数自减
          	wp.workersCount--
          	......
          }
          
          // 请求处理完之后 将当前协程的句柄channel 放入协程池的ready切片中,待下次获取协程的句柄channel的时候进行复用(复用这个句柄channel就相当于复用了对应的协程)
          func (wp *workerPool) release(ch *workerChan) bool {
          	ch.lastUseTime = time.Now()
          	wp.lock.Lock()
          	if wp.mustStop {
          		wp.lock.Unlock()
          		return false
          	}
          	wp.ready = append(wp.ready, ch)
          	wp.lock.Unlock()
          	return true
          }
          
        5. 实际处理请求的方法 server.go

          func (s *Server) serveConn(c net.Conn) (err error) {
          	.....
            //上下文对象复用并初始化上下文
            ctx := s.acquireCtx(c)
          	......
          		//执行在初始化server的时候自定义的 逻辑
          		if continueReadingRequest {
          			s.Handler(ctx)
          		}
          	.....
          	// 处理http响应
          }
          
    3. 高效的原因

      1. fastHttp 协程池 详情见 2.2.2 、2.2.4
      2. fastHttp 对象复用
        1. 协程channle对象 复用 详见2.2.4 的 getCh() 函数
        2. 上下文对象复用 详见2.2.5

    注:
    本文为自己对fastHttp的理解,如有疑问,欢迎一起讨论交流
    如需转载请注明出处:https://www.cnblogs.com/zhuchenglin/p/14358612.html

    bk