Golang源码分析之net/http(二)解析源码

1,211 阅读6分钟

Golang源码分析之net/http(二)解析源码

这是Golang源码分析之net/http主题的第二篇,我们已经在上一章节了解了TCP通信的基础框架,现在进入正真的源码分析

以下源码基于go1.14.13 windows/amd64进行分析

一、举个栗子

我们写一个C/S例子

service监听1234端口,设置路由为/hello,处理函数为HelloServer

//service.go

func main() {
	http.HandleFunc("/hello",HelloServer)
	// 监听TCP地址
	// handler通常设为nil,此时会使用DefaultServeMux(默认路由器)
	http.ListenAndServe(":1234",nil)
}
// 处理函数
func HelloServer(resp http.ResponseWriter, req *http.Request) {
	req.ParseForm()
	fmt.Println(req.Form)
	fmt.Println("path",req.URL.Path)
	fmt.Println("This is xc test")
	fmt.Fprintf(resp, "Hello xc!")
}

client设置每1s发送一个请求,并打印服务端的返回值

// client.go
func main() {
	for  {
		time.Sleep(time.Second)
		client := &http.Client{}
		req, err := http.NewRequest("GET", "http://127.0.0.1:1234/hello", nil)
		if err!=nil {
			fmt.Println("NewRequest:",err)
			return
		}
		resp,err := client.Do(req)
		if err!=nil {
			fmt.Println("client.Do:",err)
		}
		fmt.Println("resp:",resp)
	}
}

现在启动2个进程(先启服务器,再其客户端)

// service.go的日志打印
&{GET /hello HTTP/1.1 1 1 map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]] {} <nil> 0 [] false 127.0.0.1:1234 map[] map[] <nil> map[] 127.0.0.1:58176 /hello
<nil> <nil> <nil> 0xc000184a40}
ServeHTTP 0x654780
map[]
path /hello
This is xc test


// client.go的日志打印  
resp: &{200 OK 
        200 
        HTTP/1.1 
        1 
        1 
        map[Content-Length:[9] Content-Type:[text/plain; charset=utf-8] Date:[Fri, 02 Apr 2021 03:09:11 GMT]] 
        0xc0000a0040 
        9 
        [] 
        false 
        false 
        map[] 
        0xc000184100 
        <nil>
}

查看网络状态,可以看到客户端和服务端已经建立TCP连接,在双端都是ESTABLISHED状态下进行读写。LISTEN是客户端在监听客户端的接入情况。

image.png

二、源码分析

1、服务端源码
1.1、http.HandleFunc 将处理请求的函数注册到路由器map中
// 将处理函数handler 注册到默认的路由器DefaultServeMux中,服务器监听到对应的路由,会使用该handler处理
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

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
}

将pattern路由及对应的处理器以hash表形式存储,在HTTP处理请求的时候会查找对应路由的处理器

func (mux *ServeMux) Handle(pattern string, handler Handler) {
	// 加锁
	mux.mu.Lock()
	defer mux.mu.Unlock()
	...
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {
		mux.m = make(map[string]muxEntry)
	}
	// 将pattern与函数匹配,在刚才的例子中,是将"/hello"和HelloServer匹配
	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
	}
}
1.2、http.ListenAndServe 用来监听 TCP 连接并处理请求
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

ListenAndServe监听对应地址的TCP并处理对应的客户端请求

func (srv *Server) ListenAndServe() error {
	if srv.shuttingDown() {
		return ErrServerClosed
	}
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	// TCP协议搭建服务,此时就是第一章节srver创建socket、bind、listen的流程
	// HTTP的listen
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	// 监听端口
	return srv.Serve(ln)
}

这里我们看一下net.Listen接口,包含了创建socket、bind绑定socket与地址、listen端口操作。

func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {
    // syscall.Socket 系统调用创建socket
	s, err := sysSocket(family, sotype, proto)
	if err != nil {
		return nil, err
	}
	...
    // 创建该网络的描述符,完成netFD结构体的填充
	if fd, err = newFD(s, family, sotype, net); err != nil {
		poll.CloseFunc(s)
		return nil, err
	}

	if laddr != nil && raddr == nil {
		switch sotype {
        // 对于TCP 
		case syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET:
            // syscal.bind() + syscall.Listen
			if err := fd.listenStream(laddr, listenerBacklog(), ctrlFn); err != nil {
				fd.Close()
				return nil, err
			}
			return fd, nil
        // UDP的处理
		case syscall.SOCK_DGRAM:
			if err := fd.listenDatagram(laddr, ctrlFn); err != nil {
				fd.Close()
				return nil, err
			}
			return fd, nil
		}
	}
	if err := fd.dial(ctx, laddr, raddr, ctrlFn); err != nil {
		fd.Close()
		return nil, err
	}
	return fd, nil
}

监听客户端的请求,使用for循环一直等待客户端的请求。

// 监听客户端的请求
func (srv *Server) Serve(l net.Listener) error {
	...
	var tempDelay time.Duration // how long to sleep on accept failure

	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		// 接收客户端请求  rw客户请求的信息
		// Accept从处于 established 状态的连接队列头部取出一个已经完成的连接
		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
		}
		...
		c := srv.newConn(rw) // 创建一个新的连接
		c.setState(c.rwc, StateNew) // before Serve can return
		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/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 {
        // 循环读用户的请求
		w, err := c.readRequest(ctx)
		if c.r.remain != c.server.initialReadLimitSize() {
			// If we read any bytes off the wire, we're active.
			c.setState(c.rwc, StateActive)
		}
		if err != nil {
			... // 错误处理
		}
        ...

		// 处理请求
		serverHandler{c.server}.ServeHTTP(w, w.req)
		// 维测相关
        ...
	}
}

交给DefaultServeMux路由来处理

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{}
	}
    // 这里处理就是示例中注册的HelloServer
	handler.ServeHTTP(rw, req)
}
2、客户端源码

http.NewRequest NewRequest根据方法名、URL 和请求体构建请求。该方法封装成HTTP协议请求的格式,不详细展开

client.Do(req) 处理请求

对于Do方法,我们详细展开,根据层层调用最核心接口即是roundTrip。该接口发送HTTP请求并处理Resp

func (t *Transport) roundTrip(req *Request) (*Response, error) {
	// 请求检查
	...
	for {
		...
		// 获取连接,通过两种方法获取用于发送请求的连接(1、在队列中闲置的连接,2、创建新连接)
		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)
		}
		...
		// Rewind the body if we're able to.
		req, err = rewindBody(req)
		if err != nil {
			return nil, err
		}
	}
}

连接是一种相对比较昂贵的资源,如果在每次发出 HTTP 请求之前都建立新的连接,可能会消耗比较多的时间,带来较大的额外开销,通过连接池对资源进行分配和复用可以有效地提高 HTTP 请求的整体性能,多数的网络库客户端都会采取类似的策略来复用资源。

所以在获取连接(t.getConn的时候,有两种方法。

第一种:选取在队列中闲置的连接

长链接由一个map[[connectMethodKey]][][]*persistConn组成,connectMethodKey即为一个连接的key包含地址等信息,对应value为persistConn为连接的句柄。选取队列中的连接也就是获取一个句柄。若能获取成功,把该连接从队列中以及idleLRU中删除该连接。

第二种:如果空闲的连接池中没有可用的连接,则会调用queueForDial方法新建连接

使用Dial创建TCP连接,也就是发起socket的listen()。连接创建成功后,会开启两个协程,一个用于处理输入流writeLoop,一个用于处理输出流readLoop。分别从 TCP 连接中读取数据或者向 TCP 连接写入数据,从建立连接的过程我们可以发现,如果我们为每一个 HTTP 请求都创建新的连接并启动 Goroutine 处理读写数据,会占用很多的资源。

发送请求之后,roundTrip接口中pconn.roundTrip处理响应

func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
	...
	writeErrCh := make(chan error, 1)
	pc.writech <- writeRequest{req, writeErrCh, continueCh}

	resc := make(chan responseAndError)
	pc.reqch <- requestAndChan{
		req:        req.Request,
		cancelKey:  req.cancelKey,
		ch:         resc,
		addedGzip:  requestedGzip,
		continueCh: continueCh,
		callerGone: gone,
	}
	// 循环监听
	for {
		testHookWaitResLoop()
		select {
		...
		case re := <-resc:
			...
			if re.err != nil {
				return nil, pc.mapRoundTripError(req, startBytesWritten, re.err)
			}
			return re.res, nil
			...
		}
	}
}

引用