面试官:Net/Http库知道吗?能说说优缺点吗?

网站建设4年前发布
26 0 0

哈喽,大家后,我是asong;这几天看了一下Go语言标准库net/http的源码,所以就来分享一下我的学习心得;为什么会突然想看http标准库呢?因为在面试的时候面试官问我你知道Go语言的net/http库吗?他有什么有缺点吗?因为我没有看过这部分源码,所以一首凉凉送给我;,废话不多说,接下来请跟着我的脚步我们一起探索net/http;,本文代码基于:Go1.19.3,服务端:,本地启动一个server端监听8080端口,并且提供路由/profile获取个人信息;,客户端:,通过这样一个简单的例子,我们可以知道客户端我们主要使用http.Client{},服务端我们主要使用http.ListenAndServe和http.HandleFunc,所以我们就可以从这两个包入手来分别看一看客户端和服务端的代码具体是怎么封装;,客户端级别最高的抽象是net/http.Client{},具体结构如下:,客户端可以直接通过net/http.DefaultClient发起HTTP请求,也可以自己构建新的net/http.Client实现自定义的HTTP事务,多数情况下我们使用默认的客户端发出的请求就可以满足需求;,我们画一个UML图,如下所示:,20230306125855b7074e405327ff9a29d221f70e09d41dbe18d1554,image-20221204160448320,了解HTTP客户端的基本结构,我们接下来就开始分析客户端的基本实现;,net/http包的Request结构体封装好了HTTP请求所需的必要信息:,其中包含了HTTP请求的方法、URL、协议版本、协议头以及请求体等字段,还包括了指向响应的引用:Response;其提供了NewRequest()、NewRequestWithContext()两个方法用来构建请求,这个方法可以校验HTTP请求的字段并根据输入的参数拼装成新的请求结构体,NewRequest()方法内部也是调用的NewRequestWithContext,区别就是是否使用context来做goroutine上下文传递;接下来我们看一下NewRequestWithContext方法的具体实现:,整个构建请求过程中只有处理body的时候稍有一些复杂,我们需要根据body的类型使用不同的方法将其包装成io.ReadCloser类型;,构建HTTP请求后,接下来我们需要开启HTTP事务进行请求并且等待远程响应,我们以net/http.Client.Do()方法为例子,我们看一下它的调用链路:,RoundTrip()是RoundTripper类型中的一个的方法,net/http.Transport是其中的一个实现,在net/http/transport.go文件中我们可以找到这个方法:,代码一大堆,我们只要重点看两部分即可:,因为连接的建议会消耗比较多的时间,带来较大的开下,所以Go语言使用了连接池对资源进行分配和复用,先调用net/http.Transport.queueForIdleConn()获取等待闲置的连接,如果没有获取到在调用net/http.Transport.queueForDial在队列中等待建立新的连接,通过select监听连接是否建立完毕,超时未获取到连接会上剖错误,我们继续在queueForDial追踪TCP连接的建立:,我们会启动一个goroutine做tcp的建连,最终调用dialConn方法,在这个方法内做持久化连接,调用net库的dial方法进行TCP连接:,在连接建立后,代码中我们我们还看到分别启动了两个goroutine,readLoop用于从tcp连接中读取数据,writeLoop用于从tcp连接中写入数据;,我们看一下writeLoop方法:,监听writech通道,所以的数据发送都是在这个循环中写入的;,net/http.Transport{}中提供了连接池配置参数,开发者可以自行定义:,net/http.persistConn.roundTrip()会处理HTTP请求,我们看其具体实现:,我们重点关注这两个通道:,我们简单总结一下net/http库中HTTP客户端的实现:,net/http库中默认有一个DefaultClient可以直接使用,DefaultClient有对应DefaultTransport,可以满足我们大多数场景,如果需要使用自己管理HTTP客户端的头域、重定向等策略,那么可以自定义Client,如果需要管理代理、TLS配置、连接池、压缩等设置,可以自定义Transport;,因为HTTP协议的版本是不断变化的,所以为了可扩展性,transport是一个接口类型,具体的是实现是Transport、http2Transport、fileTransport,这样实现扩展性变高,值得我们学习;,HTTP在建立连接时会耗费大量的资源,需要开辟一个goroutine去创建TCP连接,连接建立后会在创建两个goroutine用于HTTP请求的写入和响应的解析,然后使用channel进行通信,所以要合理利用连接池,避免大量的TCP连接的建立可以优化性能;,我们可以用net/http库快速搭建HTTP服务,HTTP服务端主要包含两部分:,直接调用net/http.HandleFunc可以注册路由和处理函数:,我们可以看到处理函数是一个统一的格式:,默认调用HTTP服务起的DefaultServeMux处理请求,DefaultServeMux本质是ServeMux:,我看看一下路由注册函数:,net/http库提供了ListenAndServe()用来监听TCP连接并处理请求:,在这里初始化Server结构,然后调用ListenAndServe:,我们调用net网络库进行tcp连接,这里包含了创建socket、bind绑定socket与地址,listen端口的操作,最后调用Serve方法循环等待客户端的请求:,从上述代码我们可以到每个HTTP请求服务端都会单独创建一个goroutine来处理请求,我们一下处理过程:,我们继续跟踪ServeHTTP方法,ServeMux是一个HTTP请求的多路复用器,在这里可以根据请求的URL匹配合适的处理器,我们看代码:,在mux.Handler()中我们就看到了路由匹配的代码:,服务端的代码看主逻辑主要是看两部分,一个是注册处理器,标准库使用map进行存储,本质是一个静态索引,同时维护了一个切片,用来做前缀匹配,只要以/结尾的,都会在切片中存储;服务端监听端口本质也是使用net网络库进行TCP连接,然后监听对应的TCP连接,每一个HTTP请求都会开一个goroutine去处理请求,所以如果有海量请求,会在一瞬间创建大量的goroutine,这个可能是一个性能瓶颈点,所以小伙伴要注意下这块的性能问题;,net/HTTP的总体代码行数是比较多的,我们只需要看主要逻辑是怎么实现的就可以了,别人问你原理能打出来个所以然就行,不必要扣细节,当出现问题或者想具体了解某部分协议的时候在细看源码对应部分即可;,像我们过了一遍源码后我们就知道当前net/http的一些优缺点,比如优点是HTTP客户端使用了连接池,避免频繁建立带来的大开销,缺点是HTTP服务端的路由只是一个静态索引匹配,对于动态路由匹配支持的不好,并且每一个请求都会创建一个gouroutine进行处理,海量请求到来时需要考虑这块的性能瓶颈;,net/http标准库可以让我们很快的就实现一个HTTP服务器,并且也有很多我们值得借鉴学习的地方,所以源码的学习还是很必要的;

© 版权声明

相关文章