ilini

ilini 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

ilini 发布了文章 · 2月25日

在B站看猫片被老板发现?不如按下F12学学HTTP

文章持续更新,可以微信搜一搜「golang小白成长记」第一时间阅读,回复【教程】获golang免费视频教程。本文已经收录在GitHub https://github.com/xiaobaiTec... , 有大厂面试完整考点和成长路线,欢迎Star。

什么是HTTP

HTTP 全称超⽂文本传输协议,也就是HyperText Transfer Protocol。
其中我们常见的文本,图片,视频这些东西都可以用超文本进行表示,而我常看的猫片,也属于超文本,所以大家不要再说我偷偷看猫片了,我只是在看超文本。HTTP只是定义了一套传输超文本的规则,只要符合了这一套规则,不管你是用iphone,还是用老爷机,都可以实现猫片的传输。

七层网络

网络协议

大概了解了HTTP后,给大家看看它在它们家族里的地位。HTTP位于应用层,跟它类似的协议还有常见的FTP协议,常见的某影天堂的下载链接曾经经常是以FTP开头的。

HTTP报文格式

有点抽象?不知道小白说的啥?那实操一下,用wireshark抓包看一下猫片里的请求报文和响应报文具体长什么样子吧

请求报文

GET /cmaskboss/164203142_30_1.enhance.webmask HTTP/1.1
Host: upos-sz-staticks3.bilivideo.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36
Accept: */*
Origin: https://www.bilibili.com
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://www.bilibili.com/
Accept-Encoding: identity
Accept-Language: zh-CN,zh;q=0.9
Range: bytes=0-16

这上面第一行的GET 就是请求方法/cmaskboss/164203142_30_1.enhance.webmask 则是 URL , 而HTTP/1.1则是协议版本。接下来从Host开始到最后一行Range,都是Headers头

响应报文

HTTP/1.1 206 Partial Content
Content-Type: application/octet-stream
Content-Length: 17
Connection: keep-alive
Server: Tengine
ETag: "92086de1e6d1d4791fb950a0ac7e30ba"
Date: Sat, 30 Jan 2021 09:31:31 GMT
Last-Modified: Sun, 04 Oct 2020 01:54:28 GMT
Expires: Mon, 01 Mar 2021 09:31:31 GMT
Age: 1018695
Content-Range: bytes 0-16/353225
Accept-Ranges: bytes
X-Application-Context: application
x-kss-request-id: 75bcbfa8ab194e3c825e89c81a912692
x-kss-BucketOwner: MjAwMDAyMDEwNw==
X-Info-StorageClass: -
Content-MD5: kght4ebR1HkfuVCgrH4wug==
X-Cache-Status: HIT from KS-CLOUD-JH-MP-01-03
X-Cache-Status: HIT from KS-CLOUD-TJ-UN-14-13
X-Cache-Status: HIT from KS-CLOUD-LF-UN-11-25
Access-Control-Allow-Origin: https://www.bilibili.com
Access-Control-Allow-Headers: Origin,X-Requested-With,Content-Type,Accept,range
X-Cdn-Request-ID: 7e2c783ca7d392624118593ec1dc66bc

类似请求报文,HTTP/1.1协议版本206状态码Partial Content 则是状态描述符。接下来从Content-Type开始到最后一行X-Cdn-Request-ID都是Headers信息

报文信息解读

其实上面的抓包信息,在浏览器里按F12就能看到,之所以要用wireshark可能只是装X效果比较好吧。按下F12看到的响应数据就跟下图展示的那样。

1.请求数据

2.响应数据

3.Request URL

URL是什么

URL 代表着是统一资源定位符(Uniform Resource Locator)。作用是为了告诉使用者 某个资源在 Web 上的地址。这个资源可以是一个 HTML 页面,一个 CSS 文档,一幅图像或一个猫片等等。上面我们请求猫片的URL就是 https://upos-sz-staticks3.bilivideo.com/cmaskboss/164203142_30_1.enhance.webmask 这里面细分,又可以分为好几个部分。

  • 协议部分

表示该URL的协议部分为http还是https,会用//为分隔符。上面的URL表示网页用的是HTTPS协议,而上面提到的X影天堂用的则是ftp协议的下载链接。

  • 域名部分

域名是upos-sz-staticks3.bilivideo.com,在发送请求前,会向DNS服务器解析IP,如果已经知道ip,还可以跳过DNS解析那一步,直接把IP当做域名部分使用。

  • 端口部分

域名后面有些时候会带有端口,和域名之间用:分隔,端口不是一个URL的必须的部分。当网址为http://时,默认端口为80

当网址为https://时,默认端口为443,以上两种都可以省略端口号。上面的URL其实省略了443端口号。

  • 虚拟目录

从域名的第一个/开始到最后一个/为止,是虚拟目录的部分。虚拟目录也不是URL必须的部分,本例中的虚拟目录是/cmaskboss/

  • 文件名部分

从域名最后一个/开始到?为止,是文件名部分;如果没有?,则是从域名最后一个/开始到#为止,是文件名部分;如果没有?和#,那么就从域名的最后一个/从开始到结束,都是文件名部分。本例中的文件名是164203142_30_1.enhance.webmask,文件名也不是一个URL的必须部分。

URL 和 URI 的区别

  • URL:Uniform Resource Locator 统一资源定位符
  • URI: Uniform Resource Identifier 统一资源标识符

其实一直有个误解,很多人以为URI是URL的子集,其实应该反过来。URL是URI的子集才对。简单解释下。
假设"小白"(URI)是一种资源,而"在迪丽亦巴的怀里"表明了一个位置。如果你想要找到(locate)小白,那么你可以到"在迪丽亦巴怀里"找到小白,而"在迪丽亦巴怀里的/小白"才是我们常说的URL。而"在迪丽亦巴怀里的/小白"(URL)显然是"小白"(URI)的子集,毕竟,"小白"还可能是"在牛亦菲怀里的/小白"(其他URL)。

.jpg)

4.Request Method

HTTP 定义了一组请求方法,以表明要对给定资源执行的操作。指示针对给定资源要执行的期望动作.。虽然他们也可以是名词,但这些请求方法有时被称为HTTP动词.。每一个请求方法都实现了不同的语义。

这次请求B站猫片的请求里用的是GET,意味着获取。但其实HTTP定义了多种请求方法,来满足各种需求。除了Get,还有几个POST、HEAD、OPTIONS、PUT、DELETE、TRACE 和 CONNECT。

常见的各个请求方法的具体功能如下:

GET

请求指定的页面信息,并返回消息主体(body)+头信息(header)。

HEAD:

HEAD和GET本质是一样的,区别在于HEAD只返回头信息(header),不返回消息主体(body)。大家不要以为它没用,它跟GET和POST一样,在http/1.0的时候就存在了,实属三元老之一了。主要用途

  • 如果想要判断某个资源是否存在,虽然用GET也能做到,但这里用HEAD还省下拿body的消耗,返回状态码200就是有404就是无
  • 如果请求的是一个比较大的资源,比如一个超大视频和文件,你只想知道它到底有多大,而不需要整个下载下来,这时候使用HEAD请求,返回的headers会带有文件的大小(content-lenght)。

POST

向服务器提交数据。这个方法用途广泛,几乎目前所有的提交操作都是靠这个完成。POST跟GET最常用,但最大的区别在于,POST每次调用都可能会修改数据,是非幂等的,而GET类似于只读,是幂等的。

PUT:

这个方法比较少见。在HTTP规范中POST是非等幂的,多次调用会产生不同的结果。比如:创建一个用户,由于网络原因或是其他原因多创建了几次,那么将会有多个用户被创建。而PUT id/xiaobai 则会创建一个id为 xiaobai 的用户,多次调用还是会创建的结果是一样的,所以PUT是等幂的。但是一般为了避免造成心智负担,实战中也会使用POST替代PUT。

DELETE:

删除某一个资源。基本上这个也很少见,一般实战中如果是删除操作,也是使用POST来替代。

OPTIONS:

options是什么

它用于获取当前URL所支持的方法。若请求成功,则它会在HTTP响应头部中带上给各种“Allow”的头,表明某个请求在对应的服务器中都支持哪种请求方法。比如下图:

这里面需要关注的点有两个

  • Request Header里的关键字段

    .jpg)

  • Response Header里的关键字段

    .jpg)

Options堪称是网络协议中的老实人,就好像老实人刚谈了个女朋友,每次牵手前都要问下人家 “我可以牵你的手吗?”, “我可以抱你吗?”,得到了答应后才会下手。差点被这老实人气质感动得留下了不争气的泪水。

什么时候需要使用options

跨域(记住这个词,待会解释)的情况下,浏览器发起复杂请求前自动发起 options 请求。跨域共享标准规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 options 方法发起一个预检请求,从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。

这里提到了两个关键词:

  • 跨域
  • 复杂请求

什么是简单请求和复杂请求。

某些请求不会触发 CORS 预检请求,这样的请求一般称为"简单请求",而会触发预检的请求则为"复杂请求"。

  • 简单请求

    • 请求方法为GET、HEAD、POST
    • 只有以下Headers字段

      • Accept
      • Accept-Language
      • Content-Language
      • Content-Type
      • DPR/Downlink/Save-Data/Viewport-Width/Width (这些不常见,放在一起)
    • Content-Type 只有以下三种

      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain
    • 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;
    • 请求中没有使用 ReadableStream 对象。
  • 复杂请求

    • 不满足简单请求的,都是复杂请求

由此可见,因为上述请求在获取B站资源的请求Headers里带有 Access-Control-Request-Headers: range , 而range正好不在简单请求的条件2中提到的Headers范围里,因此属于复杂请求,于是触发预检options请求。

什么是跨域

刚刚提到了一个词叫跨域,那什么是跨域呢?在了解跨域之前,首先要了解一个概念:同源。所谓同源是指,域名、协议、端口均相同

不明白没关系,举个例子。

需要特别注意的是,localhost和127.0.0.1虽然都指向本机,但也不属于同源

非同源之间网页调用就是我们所说的跨域。在浏览器同源策略限制下,向不同发送XHR请求,浏览器认为该请求不受信任,禁止请求,具体表现为请求后不正常响应。

options带来什么问题

由此可见,复杂请求的条件其实非常容易满足,而一旦满足复杂请求的条件,则浏览器便会发送2次请求(一次预检options,一次复杂请求),这一次options就一来一回(一个RTT),显然会导致延迟和不必要的网络资源浪费,高并发情况下则可能为服务器带来严重的性能消耗。

.jpg)

如何优化options

每次复杂请求前都会调用一次options,这其实非常没有必要。因为大部分时候相同的请求,短时间内获得的结果是不会变的,是否可以通过浏览器缓存省掉这一次查询?

Access-Control-Max-Age就是优化这个流程中使用的一个Header。它的作用是当你每次请求options方法时,服务端返回调用支持的方法(Access-Control-Allow-Methods )和Headers(Access-Control-Allow-Headers)有哪些,同时告诉你,它在接下来 Access-Control-Max-Age时间(单位是秒)里都支持,则这段时间内,不再需要使用options进行请求。特别注意的是,当Access-Control-Max-Age的值为-1时,表示禁用缓存,每一次请求都需要发送预检请求,即用OPTIONS请求进行检测。

5.Status Code

状态码是什么

HTTP Status Code则是常说的HTTP状态码。当用户访问一个网页时,浏览器会向网页所在服务器发出请求。服务器则会根据请求作出响应,而状态码则是响应的一部分,代表着本次请求的结果。所有状态码的第一个数字代表了响应的大概含义,组合上第二第三个数字则可以表示更具体的原因。如果请求失败了,通过这个状态码,大概初步判断出这次请求失败的原因。以下是五类状态码的含义。

状态码流程

可以根据以下流程图了解下各类状态码间的关系。

  • 2xx和3xx之间的流程关系

.jpg)

  • 4xx的状态流程

  • 5xx的状态流程

.jpg)

常见状态码介绍

  • 200 OK

这是最常见的状态码。代表请求已成功,数据也正常返回。而B站猫片里虽然响应成功了,但却不是200,而是206,是为什么呢,接下去继续看看。

  • 206 Partial Content

这个状态码在上面B站请求的响应结果。服务器已经成功处理了部分 GET 请求。类似于B站看视频或者迅雷这类的 HTTP下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。

  • 307 Temporary Redirect

    内部重定向。重定向的意思是,当你输入一个网址的时候,浏览器会自动帮你跳转到另外一个网址上。比如,当你在浏览器输入框输入http://www.baidu.com/时。由于使用http并不安全,百度会自动帮你跳转到它对应的https网页上。而此时,需要重定向的地址,会通过Response HeadersLocation返回

  • 404 Not Found

    请求失败,请求所希望得到的资源未被在服务器上发现。出现这个错误的最有可能的原因是服务器端没有这个页面,或者是Request Method与注册URL的Method不一致,比如我有一个URL在服务端注册的Request Method 为 POST,但调用的时候却错误用了GET,则也会出现404错误。

  • 499 Client has closed connection

    网络请求过程中,由于服务端处理时间过长,客户端超时。一般常见于,后端服务器处理时间过长,而客户端也设置了一个超时等待时间,客户端等得“不耐烦”了,主动关掉连接时报出。

  • 502 Bad Gateway

    服务器方面无法给于正常的响应。一般常见于服务器崩溃后,nginx 无法正常收到服务端的响应,给客户端返回502状态码。

    .jpg)

  • 504 Gateway Timeout

    网络请求过程中,由于服务端处理时间过长,网关超时。一般常见于,后端服务器逻辑处理时间过长,甚至长于 nginx设置的最长等待时间时报错。它跟 499 状态码非常像,区别在于499 表示的是客户端超时,504是网关超时。如果是499超时,可以考虑修改客户端的代码调整超时时间,如果是504,则考虑调整nginx的超时配置。

    .jpg)

6. Headers

Content-Length

Content-Length是HTTP的消息长度, 用十进制数字表示。Content-Length首部指出报文中消息的当前实际字节大小。如果消息文本进行了gzip压缩的话, Content-Length指的就是压缩后的大小而不是原始大小。

正常情况下Content-Length是不需要手动去设置的,大部分语言的网络库都会自动封装好,但是如果在一些特殊情况下,出现Content-Length与实际要发送的消息大小不一致,就会出现一些问题。

  • 如果Content-Length < 实际长度

    下面启动一个HTTP服务器,所有语言都一样,示例里使用了golang。

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
    )
    
    // w表示response对象,返回给客户端的内容都在对象里处理
    // r表示客户端请求对象,包含了请求头,请求参数等等
    func index(w http.ResponseWriter, r *http.Request) {
        b, _ := ioutil.ReadAll(r.Body)
        fmt.Printf("request body=%#v, content_length=%v \nheaders=%v",string(b), r.ContentLength, r.Header)
        // 往w里写入内容,就会在浏览器里输出
        fmt.Fprintf(w, string(b))
    }
    
    func main() {
        // 设置路由,如果访问/,则调用index方法
        http.HandleFunc("/", index)
    
        // 启动web服务,监听9090端口
        err := http.ListenAndServe(":9999", nil)
        if err != nil {
            log.Fatal("ListenAndServe: ", err)
        }
    }

    在控制台输入

    $ $ curl -L -X POST 'http://127.0.0.1:9999' -H 'Content-Type: application/json' -H 'Content-Length: 5' -d '1234567' |  jq
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100    12  100     5  100     7    828   1160 --:--:-- --:--:-- --:--:--  1400
    12345

    输入的body是 1234567,共7个数字,但是输入的 Content-Length为 5。到了服务器那,收到了 12345,共5个数字,数量上跟输入的Content-Length一致。 由此可见当Content-Length < 实际长度, 消息会被截断。

  • 如果Content-Length > 实际长度

    还是上面的服务端代码,但是控制台输入以下命令

    $ curl -L -X POST 'http://127.0.0.1:9999' -H 'Content-Type: application/json' -H 'Content-Length: 100' -d '1234567' | jq
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100     7    0     0    0     7      0      0 --:--:--  0:01:19 --:--:--     0

    这次情况不太一样,会发现请求一直阻塞没有返回。这是因为输入的body是 1234567,共7个数字,但是输入的 Content-Length为 100。也就是服务端一直认为这次的body长度为100,但是目前只收到了部分消息(长度为7),剩余的长度为93的消息由于各种原因还在路上,因此选择傻傻等待剩下的消息,就造成了上面提到的阻塞。

Range

视频播放需要支持用户调整播放进度,支持让用户选择直接跳到中间部分开始播放。为了实现这个功能,需要通过HTTP Range Requests 协议用于指定需要获取视频片段。而 Request Header里的range头则是用于指定要请求文件的起始和结束位置。

  • 如果服务器支持 Range Requests 协议,会读取视频文件,并将他的第 162653~242638 字节提取出来,并以状态码 206 响应请求。

  • 如果服务器不支持,直接忽略 Range 头,读取整个文件内容,以状态码 200 响应即可。
  • 当我们在 html 中放一个 video 标签,浏览器会直接发起一个 Range: bytes=0- 的请求,向服务器请求从开始到结尾的完整文件

    • 如果服务器不支持 Range Requests,响应码为 200,浏览器会正常按流式加载整个视频文件;
    • 如果服务器支持 Range Requests,响应码为 206,则浏览器会在接收到足够字节(比如当前播放进度往后推20s)时结束掉请求,以节省网络流量;当播放进度继续往前,缓存不够时,浏览器会发起一个新的 Range Requests 请求,请求的 Range 直接从缓存结尾的字节开始,只加载剩余的部分文件。
  • 同时返回的Response Headers中有一个 content-range 的字段域,用于告诉了客户端发送了多少数据。content-range 描述了响应覆盖的范围和整个实体长度。一般格式:Content-Range: 开始字节位置-结束字节位置/文件大小(byte)

Connection

长连接和短连接

  • Connection: close

    表示请求响应完成之后立即关闭连接,这是HTTP/1.0请求的默认值。每次请求都经过“创建tcp连接 -> 请求资源 -> 响应资源 -> 释放连接”这样的过程

  • Connection: keep-alive

    表示连接不立即关闭,可以继续响应下一个请求。HTTP/1.1的请求默认使用一个持久连接。可以做到只建立一次连接,多次资源请求都复用该连接,完成后关闭。流程上是 建立tcp连接 -> 请求资源 -> 响应资源 -> ... (保持连接)... -> 第n次请求资源 -> 第n次响应资源 -> 释放连接。

在http1.1中Request Header和Reponse Header中都有可能出现一个Connection: keep-alive 头信息。Request Header里的Connection: keep-alive 头是为了告诉服务端,客户端想要以长连接形式进行通信。而Response Header里的Connection: keep-alive 头是服务端告诉客户端,我的服务器支持以长连接的方式进行通信。如果不能使用长连接,会返回 Connection: close ,相当于告诉客户端“我不支持长连接,你死了这条心,老老实实用短连接吧” 。

HTTP为什么要使用长连接

我们知道 HTTP 建立在 TCP 传输层协议之上,而 TCP 的建立需要三次握手,关闭需要四次挥手,这些步骤都需要时间,带给 HTTP 的就是请求响应时延。如果使用短连接,那么每次数据传输都需要经历一次上面提到的几个步骤,如果能只连接一次,保持住这个连接不断开,期间通信就可以省下建立连接和断开连接的过程,对于提升HTTP性能有很大的帮助。

  • 可以看到,在使用 Connection: close 通信时,每次都需要重新经历一次握手挥手。可以通过 Connection: keep-alive 省下这部分的资源消耗。

  • 长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户来说,较适用长连接。但是在长连接的应用场景下,需要有一方主动关闭连接。如果客户端和服务端之间的连接一直不关闭的话,连接数则会越来越多,严重的时候会造成资源占用过高。
  • 解决方案也比较简单。如果这些连接其实长时间内并没有任何数据传输的话,那其实属于空闲连接,这时候可以在服务端设置空闲连接的存活时间,超过一定时间后由服务端主动断掉,从而保证无用连接及时释放。

Cookies

Cookies是什么

  1. Cookie 是浏览器访问服务器后,服务器传给浏览器的一段数据。里面一般带有该浏览器的身份信息。
  2. 浏览器需要保存这段数据,不得轻易删除。
  3. 此后每次浏览器访问该服务器,都必须带上这段数据。服务器用使用这段数据确认浏览器身份信息。

Cookie的作用

Cookie 一般有两个作用。

  • 识别用户身份。

    • 举个例子。用户 A 用浏览器访问了“猫猫网”,“猫猫网”的服务器就会立刻给 A 返回一段Cookie数据,内含「uid=a」。
    • 当 A 再次访问“猫猫网”下的其他页面时,比如跳转到“猫猫交友评论”,就会附带上「uid=a」这段数据。
    • 同理,用户 B 用浏览器访问“猫猫网” 时,就给 B 分配了一段Cookie数据,内含「uid=b」。B 之后访问“猫猫网”的时候,就会一直带上「uid=b」这段数据。
    • 因此“猫猫网”的服务器通过Cookie数据就能区分 A 和 B 两个用户了。
  • 持久化用户信息。

    • 因为cookies的数据会被用户浏览器保存到本地下。因此可以利用这一特点保持一些简单的用户数据。
    • 比如一些博客网站,可以通过cookies记录下用户的性别年龄等信息,以此进行一些个性化展示。
    • 当然上面提到的都是一些比较粗糙的场景,是为了方便大家理解cookies的功能。实际使用cookies会非常谨慎。

Referrer Policy 和 Referrer

Referrer是什么

Referrer 是HTTP请求header的报文头,用于指明当前流量的来源参考页面,常被用于分析用户来源等信息。通过这个信息,我们可以知道访客是怎么来到当前页面的。比如在上面的请求截图里,可以看出我是使用https://www.bilibili.com/访问的视频资源。

Referrer Policy 是什么

  • Referrer 字段,会用来指定该请求是从哪个页面跳转页来的,里面的信息是浏览器填的。
  • 而 Referrer Policy 则是用于控制Referrer信息传不传、传哪些信息、在什么时候传的策略。

为什么要这么麻烦呢?因为有些网站一些用户敏感信息,比如 sessionid 或是 token 放在地址栏里,如果当做Referrer字段全部传递的话,那第三方网站就会拿到这些信息,会有一定的安全隐患。所以就有了 Referrer Policy,用于过滤 Referrer 报头内容。

比如在上面的请求截图里,可以看出我是使用strict-origin-when-cross-origin策略,含义是跨域时将当前页面URL过滤掉参数及路径部分,仅将协议、域名和端口(如果有的话)当作 Referrer。否则 Referrer 还是传递当前页的全路径。同时当发生降级(比如从 https:// 跳转到 http:// )时,不传递 Referrer 报头。

Cache-control

什么是cache-control

cache-control,用于控制浏览器缓存。简而言之,当某人访问网站时,其浏览器将在本地保存某些资源,例如图像和网站数据。当该用户重新访问同一网站时,缓存控制设置的规则会确定该用户是否从本地缓存中加载这些资源,或者浏览器是否必须向服务器发送新资源的请求。

什么是浏览器缓存

浏览器缓存是指浏览器本地保存网站资源,以便不必再次通过网络从服务器获取它们。例如,“猫猫网”的背景图像可以保存到本地缓存中,这样在用户第二次访问该页面时,该图像将从用户的本地文件加载,剩下网络获取资源的时间,页面加载速度就会更快。

但是浏览器也不会永远把这些网站资源放在本地,否则本地磁盘就会炸,所以会限定保存资源的时间,这叫生存时间(TTL)。如果 TTL 过期后用户请求缓存的资源,浏览器必须再次通过网络与服务器建立连接并重新下载这个资源。

常见的缓存控制策略

  • cache-control: private
    具有“private”指令的响应只能由客户端缓存,不能由中间代理(例如 CDN或代理)缓存。这些资源通常是包含私密数据的资源,例如显示用户个人信息的网站。
  • cache-control: public
    相反,“public”指令表示资源可以由任何缓存存储。
  • cache-control: no-store
    带有“no-store”指令的响应无法缓存到任何位置,也永不缓存。也就是说,用户每次请求此数据时,都必须将请求发送到源站服务器以获取新副本。此指令通常保留给包含极其敏感数据的资源,例如银行帐户信息。
  • cache-control: max-age
    此指令指定了生存时间,也就是资源在下载后可以缓存多少秒钟。例如,如果将最大期限设置为 1800,则首次从服务器请求资源后的 1800 秒(30 分钟)内,后续请求都会向用户提供该资源的缓存版本。如果 30 分钟后用户再次请求资源,则客户端需要向服务器重新请求该资源。
  • cache-control: no-cache

    从B站截图里可以看出,使用的缓存控制指令是cache-control: no-cache。它表示,只有先检查资源没有更新版本后,才可使用所请求资源的缓存版本。那么问题来了,怎么判断资源是否有更新版本呢?这就需要 ETag

ETag

Etag是 Entity tag的缩写,是服务端的一个资源版本的令牌标识。在 HTTP 响应头中将其传送到客户端。每当资源更新时,此令牌会源站服务器上更改。

  • 比如,浏览器第一次请求资源的时候,服务端返回了这个资源的ETag: "095933fff2323351d3b495f2f879616f1762f752"
  • 当浏览器再次请求这个资源的时候,浏览器会将If-None-Match: "095933fff2323351d3b495f2f879616f1762f752" 传输给服务端,服务端拿到该ETAG,对比资源是否发生变化。

    • 如果资源未发生改变,则返回304HTTP状态码,不返回具体的资源。
    • 否则表示资源已经更新,浏览器需要下载新版本以提供给用户。
  • 此过程可确保用户始终获得资源的最新版本,并且无需进行不必要的下载。

最后

果然B站是个充满学习氛围的地方,看个猫片都能学到这么多硬核知识。接下来我打算去舞蹈区看看有没有适合你们的知识点。

我是小白,有空?一起在知识的海洋里呛水啊,懂我意思?

参考资料

- [1] 计算机网络自动向下

- [2] 极客时间-趣谈网络协议

- [3] 极客时间-透视HTTP

- [4] 图解HTTP

- [5] 漫画形象-小肥柴

文章推荐:

查看原文

赞 6 收藏 4 评论 1

ilini 发布了文章 · 1月20日

程序员划水指南

文章持续更新,可以微信搜一搜「 golang小白成长记 」第一时间阅读,回复【教程】获golang免费视频教程。本文已经收录在 GitHubhttps://github.com/xiaobaiTech/golangFamily 有大厂面试完整考点和成长路线,欢迎Star。

快过年了,跟我可爱的小侄子通了个电话,上来就说,"叔叔你头发怎么变少了",我很痛心,我的小侄子,年纪轻轻的,眼神已经这么不好使了。但转念一想,这也是他对我的一种关心,作为叔叔,也该回应一把,今年过年给他带一本《少儿编程》回去当礼物吧。

但转念又一想,也许我真的头发变少了呢,我强壮的体魄不再足以支撑我肮脏的灵魂了呢,或许真的应了那句古语,程序员固有一死,要么骚死,要么累死? 我好残忍,我竟然想让我的小侄走上这么一条不归路。

可是我这一身编程本领好想传授予人,我还想不想那么快一"猝"而就。我还想看着小侄子长大成人,然后在面试他的时候偷偷放水,再把他招进部门结对编程,我一定要活久一点。我想一定还有跟我一样有未了心愿的程序员,于是今天我将祭出所有防累死技能,希望赠与有缘人

换行业

这是最直接的解决方案,那么问题来了,应该去哪个行业呢?程序员下岗后一般再就业的岗位不是送外卖就是送快递,这两个工作从工作强度和工作时长来说,丝毫不比程序员轻松。这就片面了。

有时间送快递和外卖的话,那不如老老实实考个公务员吧。程序员怎么成为公务员?这里正好有一个github项目,就教你如何成为一个公务员。

https://github.com/coder2gwy/coder2gwy

连怎么安排学习时间,怎么请假都安排得明明白白,贼吉尔细心。

跳槽到965的公司

从程序员跳槽到公务员或者是教师这条路子比较艰难,虽然我相信大部分程序员在考试上面都比常人要更擅长些。

一旦选择考公务员,等于放弃自己学习多年的技能,什么mysqlredisjava设计模式这些,都将成为过眼云烟,选择成本巨大。就算成了,也可能会后悔,或许会在某个四下无人的下午三点,用当年的程序员杯子装着这周xx办主任送的茶叶泡的茶,写着 《下班总结》,感叹这个世界少了一个出色的程序员。


那么跳槽到一个965的公司,可能可以帮助你远离累死。那么问题来了,怎么样找到995的公司?

还记得n年前的那个996icu吗?

https://github.com/996icu/996.ICU


里面贴心地给出了一份996的公司名单,如果你坚决拒绝996,那么从拒绝这份名单里的公司offer开始。

https://github.com/996icu/996.ICU/tree/master/blacklist


同时给出了这些公司996的证据,真正做到"拒绝张嘴就来"和有理有据,非常符合程序员讲事实摆道理的性格。

建议大家根据钱包厚度和身体承受能力进行选择,996下班不可怕,可怕的是,半夜一个报警就可以爬起来愉快网上冲浪了。以前高中的时候还要翻墙出去网吧通宵,现在在家里就能做到!

同时也给出了一份965的公司名单。

https://github.com/996icu/996.ICU/tree/master/whitelist

但是值得一提,这些公司都比较少,同时不排除哪一天不会变成996。毕竟制度是人定的,公司也是要盈利的,哪天换了个领导冲kpi,大家也只能一起冲冲冲了。


结论上来说,996的公司比965的公司多太多,大部分有志青年都在996的公司里,既然无法避免,那就要学会怎么在工作中防累死

学会划水

如果你已经在996的公司里,同时你的钱包需要你通过这种方式出卖劳动,那么显然换行业,跳槽,都不太适合,毕竟轻松意味着钱少。那么怎么在996公司里防累死呢?
划水成为唯一答案,划水其实也是一种技术,是分等级的,下面分享几个实用的划水技巧。

初级划水技能

初级划水技能是属于每个程序员都要学会的基础技能。

不要让自己看起来很闲

不管是因为你是真的很闲,还是因为你效率高很快把活干完了。如果你看起来很闲,老板就容易会认为你工作不饱和,从而不断加活,直到你看起来一直在忙为止。既然如此,如果你一直看起来很忙,那么这样的问题可以被很好的避免。而vscode是程序员最常用的编辑器,我们可以在此基础上找到很多划水神器。

模拟写代码
https://github.com/zy445566/vscode-plugin-swimming

代码写的快,提早完工被压榨怎么办?你需要一个模拟写代码工具,让VSCode模拟写代码,划水、摸鱼神器,让代码自己重写一遍。

看小说工具

上学那会偷偷摸摸看小说时会在小说本上套上一层课本封面,以此来掩盖上课划水的行为。同样的道理,下面这个插件感觉跟上课看小说的经历有点像,他让开发者可以在 VSCode 上边写代码边看小说。

下次等产品经理路过你身旁的时候,以为你写了一堆密密麻麻的的代码,都不好意思再给你加需求。

https://github.com/zrk1993/read-vscode-e
听音乐插件

做事做全套,还要在vscode装上听音乐的插件。

https://github.com/nondanee/vsc-netease-music
炒股

https://github.com/roblourens/vscode-stocks

中级划水技能

项目时间预估

每次需求迭代,产品都会让开发给出一个开发时间。

我接触过的百分之90的程序员都是老实的小伙子,是多少就报多少,有些为了方便产品快速看效果,还会压缩自己的时间,这种情况在新人程序员里尤为明显。

但有些不讲武德的产品,会动不动做一点 小小的改动,同时因为改动较小,之前提到的时间一般不会更改。

如果你所在的项目中产品不止一个人,就可能出现多个产品给你提需求的情况,改动不大,但挺急。

一个程序员越老实,就越不会表达,选择闷头苦干,有些甚至牺牲节假日时间在家干活。

因此为了更好的应对这些非常常见的情况,建议再估计工时的时候,都多预估至少20~50%的时间当做buffer。

如果你提前做好需求,请务必不要提前给产品看,这只会方便他在看到实物后做出更多的变更。

如何巧妙的推掉需求

很多产品在做每一期迭代开发需求的时候,会有无穷无尽的想法,而且恨不得都全部实现,最好今天就能给到。

大部分老实的程序员,会选择尽可能满足这些可爱的需求。这些永远没办法做完的需求,才是耗尽程序员心力的重要因素。

你需要给每个需求和变更排期,给需求留足buffer,确保线上出问题的时候你还有时间去处理。

让产品给这些需求排优先级,从高到低,算好每个需求的人日,做不了一定要提出来,剩下的让不同产品去pk优先级,确保你不过劳。

减少不必要的沟通

很多不明真相的产品、运营、其他部门的开发会通过各种途径找到你,大部分时候他们找你都是为了向你询问一些无关痛痒的问题。

有些时候当他们询问的问题触及技术细节的时候,新人程序员都会想要试图产生程序实现原理细节。

请记住一句话,千万不要跟不是搞技术的人聊实现细节,如果可以,最好别跟任何人聊技术实现细节,不仅会把他们的思路带跑偏,还会越解释越迷糊。

另外还有一些人遇到一些疑问,会用企业微信或钉钉找到你,如果这个人说的事情不是紧急的事情,同时没有直接找到你面前,尽量选择过半小时后再处理

你要相信他们自己解决问题的能力,大部分不重要或跟你关系不大的问题,会因为你的延缓处理自动消失

大部分真正需要你解决的问题,半小时后还会再找你一次

另外,哪怕不听音乐,带上耳机,可以减少10%的无效沟通,懂的自然懂。

提升办事效率

提升办事效率是为了减少自己无用时间的支出,从而不至于让自己没有时间在工作之余锻炼休闲。

提升效率这个事情其实比较简单。

选择适合自己的办公软件。比如印象笔记或有道云笔记来记录工作要点和备忘,用番茄学习时间来强迫自己专心写代码,同时还能装个眼睛护士定时提醒自己要放松一下眼睛。
另外我个人强烈建议大家多喝水

另外推荐几个 VSCode 效率插件

https://www.infoq.cn/article/c4wbmkc7sslz28gyazll

mac 效率神器

https://github.com/Louiszhai/tool

高级划水技能

防累死的最高技巧是提升技术水平,可以减少排查问题的时间,提升工作效率,更好的预估时间(意味着可以更精确地给自己多估两天排期)。

只有提升自身硬实力,才能打破这个死循环。

下面介绍一个golang程序员学习提升的项目,里面有超全的golang面试问题整理,用问题驱动去学习。

同时还有一张完整的入门进阶知识图谱,值得star一个啊。关于其他编程语言的学习路线,欢迎各位私信我学习地址。

https://github.com/xiaobaiTech/golangFamily

学会管理自己的时间

要学习时间管理,就要像这个领域里最顶尖的大师学习。

而这个领域里我只认罗老师,罗老师去年荣获时间管理大师称号。在业务能力上罗老师绝对是个优秀的好歌手好演员,各领域开花,忙是不可避免的,工作强度不会比程序员弱。
在此基础上,还能做到宛如影分身之术一般时间管理能力,除了熬出两个黑眼圈外,身体却十分硬朗。仔细一想,罗老师是在承受着身体和心理都在长时间承受高强度的压力,但却不影响他身心健康。

我们可以好好分析一下,罗老师为什么能在防累死这件事情上做的如此出类拔萃。个人拙见,有三点

热爱自己的事业

我们可以看下,罗老师的日程表。

这工作强度映射到程序员身上,也差不多跟连续开会写代码997差不多了吧,然而他却硬朗得很。

他到底是怎么做到的呢?一言以蔽之,热爱。解bug的过程可能是难受压抑紧张的,但是解bug成功带来的成就感真的很爽。

如果不热爱这份工作,那么成就感没了,只剩下难受和压抑了,不快乐的人,说不定哪天说没就没了

锻炼身体

他热爱跳舞,被称为“亚洲舞王”。虽然这个称呼有点夸张,但是他不是天赋型选手,跳舞能力是花时间练出来的。

而跳舞这件事情,和健身类似,让身体动起来了,出汗了,你自然就爽了。

大家应该都有一种感觉,运动,会让你出汗,会让身体巨酸巨累,但是这个过程中,会让心情变得畅快不压抑,还能让你因为敲代码而变得瘦弱的肱二头肌变得粗壮一些

事业之外还有自己的爱好

这个世界上只有一种真正的英雄主义,就是在认清生活的真相之后,还能热爱生活。

很多程序员在大学刚毕业的时候,都想着自己以后能做一个牛逼哄哄的软件,然后跟乔布斯一样改变这个世界,但是往往又在几年后选择成为一条咸鱼,每天疲惫于跟产品经理斗智斗勇,只要能砍掉哪怕一个需求,那也是一次重大胜利。

我们都只是普通人,产品经理没错,程序猿也没错,当一条咸鱼也挺香的,但是在工作中当咸鱼可以,唯唯诺诺可以,但下了班,你必须重拳出击。

你得有自己的爱好,可以是爬山旅游,篮球游泳。如果这些都太索然无味,你还可以去酒吧跟各种臭妹妹坏弟弟喝上一杯。

最后

每天看一遍累死的新闻,坚定自己划水的信念。

鲁迅曾经说过,划水最好的时候两个时间,一个是十年前,一个是现在。你懂我意思吧?

没懂? 点个在看,你的每一次扩散都可能拯救一个在累死边缘的程序猿!

查看原文

赞 0 收藏 0 评论 0

ilini 发布了文章 · 1月11日

【吐血整理】超全golang面试题合集+学习指南+知识图谱 涵盖大部分golang程序员所需要掌握的核心知识

由于外链问题,完整版可【点击】阅读原文或到 github项目 中,欢迎关注。

目录(善用Ctrl+F)

基础入门

新手

数据类型

流程控制

进阶

包管理

优化

并发编程

高级特性

问题排查

源码阅读

汇编

实践常用工具

其他

常用官方包

  • fmt - 实现格式化的输入输出操作,其中的fmt.Printf()和fmt.Println()是开发者使用最为频繁的函数。  
  • io - 实现了一系列非平台相关的IO相关接口和实现,比如提供了对os中系统相关的IO功能的封装。我们在进行流式读写(比如读写文件)时,通常会用到该包。  
  • bufio - 它在io的基础上提供了缓存功能。在具备了缓存功能后, bufio可以比较方便地提供ReadLine之类的操作。  
  • strconv - 提供字符串与基本数据类型互转的能力。  
  • os - 本包提供了对操作系统功能的非平台相关访问接口。接口为Unix风格。提供的功能包括文件操作、进程管理、信号和用户账号等。  
  • sync - 它提供了基本的同步原语。在多个goroutine访问共享资源的时候,需要使用sync中提供的锁机制。  
  • flag - 它提供命令行参数的规则定义和传入参数解析的功能。绝大部分的命令行程序都需要用到这个包。  
  • encoding/json - JSON目前广泛用做网络程序中的通信格式。本包提供了对JSON的基本支持,比如从一个对象序列化为JSON字符串,或者从JSON字符串反序列化出一个具体的对象等。  
  • http - 通过http包,只需要数行代码,即可实现一个爬虫或者一个Web服务器,这在传统语言中是无法想象的。  

常用第三方包

必看项目

其他优秀的开源工具分类

音频和音乐

  • EasyMIDI - EasyMidi是一个简单可靠的库,用于处理标准Midi文件(SMF)。
  • flac - 支持FLAC流的Native Go FLAC编码器/解码器。
  • gaad - 本机Go AAC比特流解析器。
  • go-sox - 用于go的libsox绑定。
  • go_mediainfo - 用于go的libmediainfo绑定。
  • gosamplerate - 用于go的libsamplerate绑定。
  • id3v2 - 用于Go的快速,稳定的ID3解析和编写库。
  • malgo - 迷你音频库。
  • minimp3 - 轻量级MP3解码器库。
  • mix - 为音乐应用程序基于序列转到本地音频混合器。
  • mp3 - Native Go MP3解码器。
  • music-theory - Go中的音乐理论模型。
  • Oto - 在多个平台上播放声音的低级库。
  • PortAudio - 用于PortAudio音频I / O库的绑定。
  • portmidi - 绑定PortMidi。
  • taglib - 为taglib绑定。
  • vorbis - “本机” Go Vorbis解码器(使用CGO,但没有依赖项)。
  • waveform - Go程序包,能够从音频流生成波形图像。

数据结构

  • algorithms - 算法和数据结构。CLRS研究。
  • binpacker - 二进制打包程序和解包程序可帮助用户构建自定义二进制流。
  • bit - 具有额外的位旋转功能的Golang设置数据结构。
  • bitset - 实现位集的Go包。
  • bloom - 在Go中实现的Bloom过滤器。
  • bloom - Golang Bloom过滤器实现。
  • boomfilters - 用于处理连续无界流的概率数据结构。
  • concurrent-writer - 高并发直接替换bufio.Writer。
  • conjungo - 一个小型,强大而灵活的合并库。
  • count-min-log - 执行Count-Min-Log草图:使用近似计数器进行近似计数(类似于Count-Min草图,但使用较少的内存)。
  • crunch - Go包实现了用于轻松处理各种数据类型的缓冲区。
  • cuckoofilter - Cuckoo过滤器:是Go中实现的计数布隆过滤器的很好替代。
  • deque - 高度优化的双端队列。
  • deque - 快速的环形缓冲区双端队列(双端队列)。
  • dict - Go的类似Python的字典(dict)。
  • encoding - Go的整数压缩库。
  • go-adaptive-radix-tree - 自适应基数树的 Go实现。
  • go-datastructures - 有用,高性能和线程安全的数据结构的集合。
  • go-ef - Elias-Fano编码的Go实现。
  • go-geoindex - 内存中的地理索引。
  • go-mcache - 快速内存键:值存储/缓存库。指针缓存。
  • go-rquad - 具有有效点定位和邻居发现功能的区域四叉树。
  • gocache - 具有多个存储(内存,memcache,redis等),可链接,可加载,指标缓存等的完整Go缓存库。
  • goconcurrentqueue - 并发FIFO队列。
  • gods - 数据结构。容器,集合,列表,堆栈,地图,BidiMap,树,HashSet等。
  • gofal - Go的小数api。
  • golang-set - Go的线程安全和非线程安全高性能集。
  • goset - Go的有用的Set集合实现。
  • goskiplist - Go中的跳过列表实现。
  • gota - Go的数据框,序列和数据整理方法的实现。
  • hide - ID类型,将其编组进/出哈希以防止将ID发送给客户端。
  • hilbert - Go程序包,用于在空间填充曲线(例如Hilbert和Peano曲线)之间映射值。
  • hyperloglog - HyperLogLog实施,具有稀疏,LogLog-Beta偏差校正和TailCut空间减少功能。
  • iter - C ++ STL迭代器和算法的实现。
  • levenshtein - Levenshtein距离和相似性度量标准,具有可自定义的编辑费用和通用前缀的类似于Winkler的奖金。
  • levenshtein - 在Go中计算levenshtein距离的实现。
  • mafsa - 具有最小完美散列的MA-FSA实现。
  • merkletree - merkle树的实现,可对数据结构的内容进行有效且安全的验证。
  • mspm - 用于信息检索的多字符串模式匹配算法。
  • null - 可空转到类型,可以被编组/解组到/从JSON。
  • parsefields - 用于解析类似JSON的日志的工具,以收集唯一的字段和事件。
  • pipeline - 具有扇入和扇出的管线的实现。
  • ptrie - 前缀树的实现。
  • remember-go - 缓存慢速数据库查询的通用接口(由redis,memcached,ristretto或内存支持)。
  • ring - 围棋实现了高性能,线程安全的布隆过滤器。
  • roaring - 实施压缩位集的软件包。
  • set - 使用LinkedHashMap的围棋设置简单的数据结构实现。
  • skiplist - 非常快的Go Skiplist实施。
  • skiplist - Go中的跳过列表实现。
  • timedmap - 具有过期的键/值对的地图。
  • treap - 使用树堆的持久快速排序的地图。
  • trie - Go中的Trie实现。
  • ttlcache - 内存中的LRU字符串接口{}映射,其中包含golang的到期时间。
  • typ - 空类型,安全的原始类型转换和从复杂结构中获取值。
  • willf/bloom - Go包实现Bloom过滤器。

分布式系统

  • celeriac - 用于在Go中添加支持以交互和监视Celery工作者,任务和事件的库。
  • consistent - 具有受限负载的一致哈希
  • dht - BitTorrent Kademlia DHT实施。
  • digota - grpc电子商务微服务。
  • dot - 使用操作转换/ OT进行分布式同步。
  • doublejump - 改进后的Google的跳转一致性哈希。
  • dragonboat - Go中功能齐全的高性能多组Raft库。
  • drmaa - 基于DRMAA标准的集群调度程序的作业提交库。
  • dynamolock - DynamoDB支持的分布式锁定实现。
  • dynatomic - 将DynamoDB用作原子计数器的库。
  • emitter-io - 使用MQTT,Websockets和love构建的高性能,分布式,安全和低延迟的发布-订阅平台。
  • flowgraph - 基于流的编程包。
  • gleam - 用纯围棋和Luajit快速和可扩展的分布式的map / reduce系统,具有Luajit的高性能结合Go的高并发,单独运行或分发。
  • glow - 易于使用的可扩展的分布式大数据处理,Map-Reduce,DAG执行,全部在纯Go中进行。
  • go-health - health-用于在服务中启用异步依赖项运行状况检查的库。
  • go-jump - Google的“ Jump”一致性哈希函数的端口。
  • go-kit - 支持服务发现,负载平衡,可插拔传输,请求跟踪等的微服务工具包
  • go-sundheit - 建立用于支持为golang服务定义异步服务运行状况检查的库。
  • gorpc - 简单,快速和可扩展的RPC库,可实现高负载。
  • grpc-go - gRPC的Go语言实现。基于HTTP / 2的RPC。
  • hprose - 十分新颖的RPC库,现在支持25种以上的语言。
  • jsonrpc - jsonrpc软件包可帮助实现JSON-RPC 2.0。
  • jsonrpc - JSON-RPC 2.0 HTTP客户端实现。
  • KrakenD - 具有中间件的超高性能API网关框架。
  • liftbridge - NATS的轻量级,容错消息流。
  • micro - 可插拔的microService工具箱和分布式系统平台。
  • NATS - 用于微服务,IoT和云本机系统的轻量级高性能消息传递系统。
  • outboxer - Outboxer是一个实现库模式的go库。
  • pglock - PostgreSQL支持的分布式锁定实现。
  • raft - HashiCorp的Raft共识协议的Golang实现。
  • raft - 围棋实施筏一致协议,由CoreOS的。
  • rain - BitTorrent客户端和库。
  • redis-lock - 使用Redis的简化分布式锁定实现。
  • resgate - 用于构建REST,实时和RPC API的实时API网关,其中所有客户端都可以无缝同步。
  • ringpop-go - Go应用程序的可扩展,容错应用程序层分片。
  • rpcx - 分布式可插拔RPC服务框架,例如阿里巴巴Dubbo。
  • sleuth - 用于在HTTP服务之间进行无主p2p自动发现和RPC的库(ZeroMQ)。
  • tendermint - 高性能中间件,用于使用Tendermint共识和区块链协议将以任何编程语言编写的状态机转换为拜占庭容错复制状态机。
  • torrent - BitTorrent客户端软件包。

电子邮件

  • chasquid - 用Go编写的SMTP服务器。
  • douceur - CSS内衬为您的HTML电子邮件。
  • email - 用于Go的强大而灵活的电子邮件库。
  • go-dkim - DKIM库,用于签名和验证电子邮件。
  • go-imap - 用于客户端和服务器的IMAP库。
  • go-message - Internet消息格式和邮件消息的流库。
  • go-premailer - Go中HTML邮件的内联样式。
  • go-simple-mail - 使用SMTP保持活动状态和两个超时发送电子邮件的非常简单的程序包:连接和发送。
  • Hectane - 提供HTTP API的轻型SMTP客户端。
  • hermes - Golang软件包,可生成干净的响应式HTML电子邮件。
  • mailchain - 将加密的电子邮件发送到用Go编写的区块链地址。
  • mailgun-go - Go库,用于使用Mailgun API发送邮件。
  • MailHog - 通过Web和API界面进行电子邮件和SMTP测试。
  • SendGrid - SendGrid的Go库,用于发送电子邮件。
  • smtp - SMTP服务器协议状态机。

嵌入式脚本语言

  • anko - 用Go语言编写的可编写脚本的解释器。
  • binder - 转到基于gopher-lua的 Lua绑定库。
  • cel-go - 具有渐进式输入功能的快速,便携式,非图灵完整表达评估。
  • expr - 可以评估表达式的引擎。
  • gentee - 可嵌入的脚本编程语言。
  • gisp - Go中的简单LISP。
  • go-duktape - Go的Duktape JavaScript引擎绑定。
  • go-lua - Lua 5.2 VM到纯Go的端口。
  • go-php - Go的PHP绑定。
  • go-python - 与CPython C-API的幼稚go绑定。
  • golua - Lua C API的绑定。
  • gopher-lua - 用Go编写的Lua 5.1 VM和编译器。
  • gval - 用Go编写的高度可定制的表达语言。
  • ngaro - 可嵌入的Ngaro VM实现,支持在Retro中编写脚本。
  • otto - 用Go编写的JavaScript解释器。
  • purl - Go中嵌入的Perl 5.18.2。
  • tengo - 用于Go的字节码编译脚本语言。

错误处理

  • emperror - Go库和应用程序的错误处理工具和最佳实践。
  • errlog - 可破解的软件包,用于确定错误的负责任的源代码(以及其他一些快速调试功能)。可插入任何现成的记录器。
  • errors - 下拉更换为标准库的错误包和github.com/pkg/errors。提供各种错误处理原语。
  • errors - 提供简单错误处理原语的软件包。
  • errors - 简单golang错误处理与分类元。
  • errorx - 具有堆栈跟踪,错误组成等的功能丰富的错误包。
  • Falcon - 一个简单但功能强大的错误处理软件包。
  • go-multierror - Go(golang)软件包,用于将错误列表表示为单个错误。
  • tracerr - 带有堆栈跟踪和源代码片段的Golang错误。
  • werr - 错误包装程序为Go中的错误类型创建了一个包装程序,该包装程序捕获了调用它的文件,行和堆栈。

文件

  • afero - Go的文件系统抽象系统。
  • afs - Go的抽象文件存储(mem,scp,zip,tar,云:s3,gs)。
  • bigfile - 文件传输系统,支持使用http api,rpc调用和ftp客户端管理文件。
  • checksum - 计算大型文件的消息摘要,例如MD5和SHA256。
  • flop - 文件操作库,旨在与GNU cp镜像功能奇偶校验。
  • go-csv-tag - tag-使用标签加载csv文件。
  • go-decent-copy - 复制human文件。
  • go-exiftool - ExifTool的Go绑定,这是众所周知的库,用于从文件(图片,PDF,office,...)提取尽可能多的元数据(EXIF,IPTC等)。
  • go-gtfs - 在go中加载gtfs文件。
  • notify - 具有简单API的文件系统事件通知库,类似于os / signal。
  • opc - 为Go加载Open Packaging Conventions(OPC)文件。
  • parquet - 读取和写入 parquet文件。
  • pdfcpu - PDF 处理器。
  • skywalker - 一种软件包,允许一个人轻松地同时通过文件系统。
  • stl - 读取和写入STL(立体光刻)文件的模块。并发读取算法。
  • tarfs - tar文件FileSystem interface接口的实现。
  • vfs - 跨多种文件系统类型(例如os,S3和GCS)的Go的一组可插拔,可扩展且自以为是的文件系统功能。

金融

  • accounting - golang的货币和货币格式。
  • currency - 高性能和准确的货币计算包。
  • decimal - 任意精度定点十进制数字。
  • go-finance - Go中的综合金融市场数据。
  • go-finance - 金融功能库,用于货币时间价值(年金),现金流量,利率转换,债券和折旧计算。
  • go-finance - 获取汇率,通过VIES检查增值税号和检查IBAN银行帐号的模块。
  • go-money - Fowler的Money模式的实现。
  • ofxgo - 查询OFX服务器和/或解析响应(使用示例命令行客户端)。
  • orderbook - 匹配引擎的限价订单在Golang。
  • techan - 具有高级市场分析和交易策略的技术分析库。
  • transaction - 以多线程模式运行的嵌入式帐户嵌入式事务数据库。
  • vat - 增值税号验证和欧盟增值税率。

游戏开发

  • Azul3D - 用Go语言编写的3D游戏引擎。
  • Ebiten - Go中死的简单2D游戏库。
  • engo - Engo是用Go语言编写的开源2D游戏引擎。它遵循实体组件系统范式。
  • g3n - Go 3D游戏引擎。
  • GarageEngine - 用Go语言编写的2D游戏引擎,可在OpenGL上使用。
  • glop - Glop(权力游戏库)是一个相当简单的跨平台游戏库。
  • go-astar - A 路径查找算法的Go实现。
  • go-collada - Go包,用于Collada文件格式。
  • go-sdl2 - Simple DirectMedia Layer的 Go绑定。
  • go3d - 用于Go的面向性能的2D/3D数学软件包。
  • gonet - 使用golang实现的游戏服务器框架。
  • goworld - 可扩展的游戏服务器引擎,具有空间实体框架和热插拔功能。
  • Leaf - 轻量级游戏服务器框架。
  • nano - 重量轻,设备,高性能的基于golang游戏服务器架构。
  • Oak - Pure Go游戏引擎。
  • Pitaya - 可扩展的游戏服务器框架,具有群集支持和通过C SDK的iOS,Android,Unity等客户端库。
  • Pixel - Go中的手工制作2D游戏库。
  • raylib-go - 去绑定raylib,简单和易于使用的库,以了解电子游戏编程。
  • termloop - Go的基于终端的游戏引擎,建立在Termbox之上。

地理位置

  • geocache - 适用于基于地理位置的应用程序的内存中缓存。
  • geoserver - geoserver是Go软件包,用于通过GeoServer REST API操纵GeoServer实例。
  • gismanager - 将 GIS数据(矢量数据)发布到PostGIS和Geoserver。
  • osm - 用于读取,编写和使用OpenStreetMap数据和API的库。
  • pbf - OpenStreetMap PBF golang编码器/解码器。
  • S2 geometry - Go中的S2几何库。
  • Tile38 - 具有空间索引和实时地理围栏的地理位置数据库。
  • WGS84 - 库坐标转换和变换(ETRS89,OSGB36,NAD83,RGF93,网络墨卡托UTM)。

编译器

  • c4go - 将C代码转换为Go代码。
  • f4go - 将FORTRAN 77代码转换为Go代码。
  • gopherjs - 从Go到JavaScript的编译器。
  • llgo - Go的基于LLVM的编译器。
  • tardisgo - Golang转换为CPP / CSharp / Java / JavaScript转译器。

Goroutines

  • ants - 用于golang的高性能goroutine池。
  • artifex - Golang使用基于工作程序的分派的简单内存中作业队列。
  • async - 一种异步执行功能的安全方法,以防万一。
  • breaker - 使执行流程可中断的灵活机制。
  • cyclicbarrier - 用于golang的CyclicBarrier。
  • go-floc - 轻松编排goroutine。
  • go-flow - 控制goroutine的执行顺序。
  • go-tools/multithreading - 使用带有简单API的轻量级库管理goroutine池。
  • go-trylock - 支持Golang的读写锁的TryLock。
  • go-waitgroup - sync.WaitGroup与错误处理和并发控制类似。
  • gohive - Go的高性能和易于使用的Goroutine池。
  • gollback - 异步简单函数实用程序,用于管理闭包和回调的执行。
  • GoSlaves - 简单和异步Goroutine池库。
  • goworker - goworker是基于Go的后台工作者。
  • gowp - gowp是并发限制goroutine池。
  • gpool - 管理可调整大小的上下文感知goroutine池以绑定并发。
  • grpool - 轻巧的Goroutine池。
  • Hunch - 预感提供功能,如:All,First,Retry,Waterfall等等,这使得异步流控制更加直观。
  • oversight - 监督是Erlang监督树的完整实现。
  • parallel-fn - 并行运行功能。
  • pool - 有限的消费者goroutine池或无限制的goroutine池,以便更轻松地处理和取消goroutine。
  • queue - 为您提供sync.WaitGroup类似的队列组可访问性。帮助您节流和限制goroutine,等待所有goroutine结束等等。
  • routine - 具有上下文和支持的例程控制:Main,Go,Pool和一些有用的Executors。
  • semaphore - 基于通道和上下文的具有锁定/解锁操作超时的信号量模式实现。
  • semaphore - 基于CAS的快速可调整大小的信号量实现(比基于通道的信号量实现更快)。
  • stl - 基于软件交易内存(STM)并发控制机制的软件交易锁。
  • threadpool - Golang线程池实现。
  • tunny - 线程池golang。
  • worker-pool - goworker是一个简单的Go异步工作池。
  • workerpool - Goroutine池,它限制了任务执行的并发性,而不是排队的任务数。

图形界面

  • app - 打包以使用GO,HTML和CSS创建应用的程序。支持:MacOS,Windows正在开发中。
  • fyne - 为Go设计的跨平台本机GUI,使用EFL呈现。支持:Linux,macOS,Windows。
  • go-astilectron - 使用GO和HTML / JS / CSS(由Electron支持)构建跨平台GUI应用。
  • go-gtk - GTK的绑定。
  • go-sciter - Go绑定:用于现代桌面UI开发的可嵌入HTML / CSS / script引擎。跨平台。
  • gotk3 - GTK3的绑定。
  • gowd - 使用GO,HTML,CSS和NW.js进行快速简单的桌面UI开发。跨平台。
  • qt - Go的Qt绑定(支持Windows / macOS / Linux / Android / iOS / Sailfish OS / Raspberry Pi)。
  • ui - Go的平台本地GUI库。跨平台。
  • Wails - 使用内置OS HTML渲染器的HTML UI的Mac,Windows,Linux桌面应用程序。
  • walk - Go的Windows应用程序库工具包。
  • webview - 具有简单双向JavaScript绑定的跨平台Webview窗口(Windows / macOS / Linux)。
  • go-appindicator - libappindicator3 C库的Go绑定。
  • gosx-notifier - Go的OSX桌面通知库。
  • mac-activity-tracker - OSX库,用于通知计算机上的任何(可插入)活动。
  • mac-sleep-notifier - golang中的OSX睡眠/唤醒通知。
  • robotgo - Go本机跨平台GUI系统自动化。控制鼠标,键盘等。
  • systray - 跨平台的Go库,用于在通知区域中放置图标和菜单。
  • trayhost - 跨平台的Go库,用于在主机操作系统的任务栏中放置一个图标。

图片

  • bild  - 纯Go中图像处理算法的集合。
  • bimg  - 使用libvips进行快速有效的图像处理的小包装。
  • cameron  - Go的头像生成器。
  • canvas  - 将矢量图形转换为PDF,SVG或光栅图像。
  • darkroom  - 具有可变存储后端的图像代理和侧重于速度和弹性的图像处理引擎。
  • geopattern  - 从字符串创建漂亮的生成图像图案。
  • gg  - 纯Go中的2D渲染。
  • gift  - 图像处理过滤器的包装。
  • gltf  - 高效,强大的glTF 2.0读取器,写入器和验证器。
  • go-cairo  - 用于cairo图形库的绑定。
  • go-gd  - GD库的Go绑定。
  • go-nude  - Go的裸露检测。
  • go-opencv  - 用于OpenCV的绑定。
  • go-webcolors  - webcolors库的端口,从Python到Go。
  • gocv  - 使用OpenCV 3.3+进行计算机视觉的Go软件包。
  • goimagehash  - Go感知图像哈希包。
  • goimghdr  - imghdr模块确定Go文件中包含的图像类型。
  • govatar  - 用于生成有趣头像的库和CMD工具。
  • image2ascii  - 将图像转换为ASCII。
  • imagick  - 绑定到ImageMagick的MagickWand C API。
  • imaginary  - 用于图像大小调整的快速,简单的HTTP微服务。
  • imaging  - 简单的Go图像处理包。
  • img  - 选择图像处理工具。
  • ln  - Go中的3D线条艺术渲染。
  • mergi  - 用于图像处理(合并,裁切,调整大小,水印,动画)的Tool&Go库。
  • mort  - 用Go编写的存储和图像处理服务器。
  • mpo  - 用于MPO 3D照片的解码器和转换工具。
  • picfit  - 用Go编写的图像大小调整服务器。
  • pt  - 用Go语言编写的路径跟踪引擎。
  • resize  - 使用常见的插值方法为Go 调整图像大小。
  • rez  - 在纯Go和SIMD中调整图像大小。
  • smartcrop  - 查找适合任何图像和尺寸的优质作物。
  • steganography  - 用于LSB隐写术的Pure Go库。
  • stegify  - 用于LSB隐写术的Go工具,能够隐藏图像中的任何文件。
  • svgo  - 用于SVG生成的Go语言库。
  • tga  - 软件包tga是TARGA图像格式的解码器/编码器。

物联网

  • connectordb  - 量化自我和物联网的开源平台。
  • devices  - IoT设备库套件,针对x / exp / io进行实验。
  • eywa  - Project Eywa本质上是一个连接管理器,用于跟踪连接的设备。
  • flogo  - Project Flogo是一个用于IoT Edge应用和集成的开源框架。
  • gatt  - 盖特是一个围棋包构建低功耗蓝牙外设。
  • gobot  - Gobot是机器人技术,物理计算和物联网的框架。
  • huego  - 适用于Go的飞利浦Hue扩展客户端库。
  • iot  - IoT是用于实现Google IoT Core设备的简单框架。
  • mainflux  - 工业物联网消息和设备管理服务器。
  • periph  - 外设I / O与低级别的主板设备接口。
  • sensorbee  - 用于物联网的轻量级流处理引擎。

JSON格式

  • ajson  - 具有JSONPath支持的golang的抽象JSON。
  • gjo  - 用于创建JSON对象的小型实用程序。
  • GJSON  - 使用一行代码获取JSON值。
  • go-jsonerror  - Go-JsonError可让我们轻松创建遵循JsonApi规范的json响应错误。
  • go-respond  - Go包,用于处理常见的HTTP JSON响应。
  • gojq  - Golang中的 JSON查询。
  • gojson  - 从示例JSON自动生成Go(golang)结构定义。
  • JayDiff  - 用Go编写的JSON diff实用程序。
  • jettison  - 用于Go的高性能,无反射JSON编码器。
  • JSON-to-Go  - 将JSON转换为Go结构。
  • json2go  - 高级JSON到Go结构转换。提供可以解析多个JSON文档并创建适合所有JSON的结构的包。
  • jsonapi-errors  - 根据JSON API错误参考进行绑定。
  • jsonf  - 突出显示格式和获取JSON的结构查询的控制台工具。
  • jsongo  - Fluent API,可以更轻松地创建Json对象。
  • jsonhal  - 简单的Go包,用于将自定义结构编组为HAL兼容的JSON响应。
  • kazaam  - 用于JSON文档的任意转换的API。
  • mp  - 简单的cli电子邮件解析器。当前,它使用标准输入并输出JSON。

机器学习

  • bayesian  - 贝叶斯分类为Golang天真。
  • CloudForest  - 快速,灵活,多线程的决策树集合,用于纯Go中的机器学习。
  • eaopt  - 进化优化库。
  • evoli  - 遗传算法和粒子群优化库。
  • fonet  - 用Go编写的深度神经网络库。
  • go-cluster  - k模式和k-原型聚类算法的Go实现。
  • go-deep  - Go中功能丰富的神经网络库
  • go-fann  - 快速人工神经网络(FANN)库的Go绑定。
  • go-galib  - 用Go / golang编写的遗传算法库。
  • go-pr  - Go lang中的模式识别包。
  • gobrain  - 用go语言编写的神经网络
  • godist  - 各种概率分布及相关方法。
  • goga  - Go的遗传算法库。
  • GoLearn  - 用于Go的通用机器学习库。
  • golinear  - Go的liblinear绑定。
  • GoMind  - Go中的简单神经网络库。
  • goml  - Go中的在线机器学习。
  • Goptuna  - 用于Go语言编写的黑盒函数的贝叶斯优化框架。一切都会被优化。
  • goRecommend  - 用Go编写的推荐算法库。
  • gorgonia  - 基于图形的计算库,例如Theano for Go,它提供了用于构建各种机器学习和神经网络算法的原语。
  • gorse  - 基于Go编写的协作过滤的离线推荐系统后端。
  • goscore  - 用于PMML的Go Scoring API。
  • gosseract  - 使用Tesseract C ++库的OCR(光学字符识别)软件包。
  • libsvm  - 基于LIBSVM 3.14 libsvm的golang版本衍生作品。
  • neat  - 用于增强拓扑神经演化(NEAT)的即插即用,并行Go框架。
  • neural-go  - go-在Go中实现的多层感知器网络,通过反向传播进行训练。
  • ocrserver  - 一个简单的OCR API服务器,非常容易被Docker和Heroku部署。
  • onnx-go  - 转到开放神经网络交换(ONNX)的接口。
  • probab  - 概率分布函数。贝叶斯推断。用纯Go语言编写。
  • regommend  - 建议和协作过滤引擎。
  • shield  - 贝叶斯文本分类器,具有灵活的标记器和Go的存储后端。
  • tfgo  - 易于使用的Tensorflow绑定:简化了官方Tensorflow Go绑定的使用。在Go中定义计算图,加载并执行经过Python训练的模型。
  • Varis  - Golang神经网络。

金融

  • unioffice  - Pure Go库,用于创建和处理Office Word(.docx),Excel(.xlsx)和Powerpoint(.pptx)文档。
  • excelize  - Golang库用于读取和写入Microsoft Excel™(XLSX)文件。
  • go-excel  - 一个简单而轻便的阅读器,可以将类似于related-db的excel读取为表格。
  • goxlsxwriter  - libxlsxwriter的Golang绑定,用于编写XLSX(Microsoft Excel)文件。
  • xlsx  - 用于简化在Go程序中读取Microsoft Excel最新版本使用的XML格式的库。
  • xlsx  - 在Go程序中快速/安全地读取/更新您现有的Microsoft Excel文件的方法。

自然语言处理

  • getlang  - 快速自然语言检测程序包。
  • go-i18n  - 用于处理本地化文本的软件包和一个随附工具。
  • go-mystem  - CGo与Yandex.Mystem的绑定-俄罗斯形态分析仪。
  • go-nlp  - 用于处理离散概率分布的实用程序和其他可用于执行NLP工作的工具。
  • go-pinyin  - CN Hanzi至Hanyu拼音转换器。
  • go-stem  - 搬运程序阻止算法的实现。
  • go-unidecode  - Unicode文本的ASCII音译。
  • go2vec  - 用于word2vec嵌入的阅读器和实用程序功能。
  • gojieba  - 这是一个围棋实施解霸其中中国分词算法。
  • golibstemmer  - 雪球库libstemmer库的绑定,包括porter 2。
  • gotokenizer  - 基于字典和Goram语言的Bigram语言模型的标记器。(现在仅支持中文细分)
  • gounidecode  - Go的Unicode音译器(也称为unidecode)。
  • gse  - 进行有效的文本分割;支持英语,中文,日语等。
  • icu  - CGO结合为ICU4C C库检测和转换功能。保证与版本50.1兼容。
  • kagome  - 用纯Go语言编写的JP形态分析仪。
  • libtextcat  - libtextcat C库的Cgo绑定。保证与2.2版兼容。
  • MMSEGO  - 这是MMSEG的GO实现,它是中文分词算法。
  • nlp  - 从字符串中提取值,并用nlp填充您的结构。
  • nlp  - 支持LSA(潜在语义分析)的自然语言处理库。
  • paicehusk  - Paice / Husk提取算法的Golang实现。
  • petrovich  - 彼得罗维奇(Petrovich)是库,在给定的语法情况下使用俄语名称。
  • porter  - 这是Martin Porter的Porter干算法的C实现的相当简单的移植。
  • porter2  - 非常快的Porter 2 提取器。
  • prose  - 用于文本处理的库,支持标记化,词性标记,命名实体提取等。仅限英语。
  • RAKE.go  - 快速自动关键字提取算法(RAKE)的Go端口。
  • segment  - 用于执行Unicode标准附件#29中所述的Unicode文本分段的Go库
  • sentences  - 句子标记器:将文本转换为句子列表。
  • shamoji  - shamoji是用Go编写的单词过滤程序包。
  • snowball  - Go的雪球茎端口(cgo包装器)。提供单词词干提取功能Snowball本机。
  • stemmer  - 用于Go编程语言的Stemmer软件包。包括英语和德语词干。
  • textcat  - Go软件包,用于基于n-gram的文本分类,并支持utf-8和原始文本。
  • whatlanggo  - Go的自然语言检测程序包。支持84种语言和24种脚本(书写系统,例如拉丁语,西里尔字母等)。
  • when  - 自然EN和RU语言日期/时间分析器具有可插拔的规则。

网络

  • arp - 包arp实现ARP协议,如RFC 826中所述。
  • buffstreams  - 通过TCP流化协议缓冲区数据变得容易。
  • canopus  - CoAP客户端/服务器实施(RFC 7252)。
  • cidranger  - Go的快速IP到CIDR查找。
  • dhcp6  - 软件包dhcp6实现了DHCPv6服务器,如RFC 3315中所述。
  • dns  - 使用DNS的Go库。
  • ether  - 用于发送和接收以太网帧的跨平台Go软件包。
  • ethernet  - 程序包ethernet实施IEEE 802.3以太网II帧和IEEE 802.1Q VLAN标签的封送处理。
  • fasthttp  - 软件包fasthttp是Go的一种快速HTTP实现,比net / http快10倍。
  • fortio  - 负载测试库和命令行工具,高级回显服务器和Web UI。允许指定设置的每秒查询负载,并记录延迟直方图和其他有用的统计数据并对其进行图形化。Tcp,Http,gRPC。
  • ftp -  程序包ftp实现RFC 959中所述的FTP客户端。
  • gev  - gev是基于Reactor模式的轻量级,快速,无阻塞的TCP网络库。
  • gmqtt  - Gmqtt是一个灵活的高性能MQTT代理库,它完全实现了MQTT协议V3.1.1。
  • gnet  - gnet是一个高性能的,用纯围棋轻便,非阻塞,事件循环网络库。
  • gNxI  - 使用gNMI和gNOI协议的网络管理工具的集合。
  • go-getter  - Go库,用于使用URL从各种来源下载文件或目录。
  • go-powerdns  - Golang的 PowerDNS API绑定。
  • go-stun  - STUN客户端的Go实现(RFC 3489和RFC 5389)。
  • gobgp  - 使用Go编程语言实现的BGP。
  • golibwireshark  - 软件包golibwireshark使用libwireshark库来解码pcap文件并分析解剖数据。
  • gopacket  - Go库,用于使用libpcap绑定进行数据包处理。
  • gopcap  - libpcap的包装器。
  • goshark  - 软件包goshark使用tshark解码IP数据包并创建数据结构以分析数据包。
  • gosnmp  - 用于执行SNMP操作的本机Go库。
  • gosocsvr  - 套接字服务器变得简单。
  • gotcp  - 用于快速编写tcp应用程序的Go软件包。
  • grab  - 用于管理文件下载的软件包。
  • graval  - 实验性FTP服务器框架。
  • HTTPLab  - HTTPLabs可让您检查HTTP请求并伪造响应。
  • iplib  - 受python ipaddress和ruby ipaddr启发而使用IP地址(net.IP,net.IPNet)的库
  • jazigo  - Jazigo是用Go语言编写的工具,用于检索多个网络设备的配置。
  • kcp-go  - KCP-快速可靠的ARQ协议。
  • kcptun  - 基于KCP协议的极其简单和快速的udp隧道。
  • lhttp  - 强大的websocket框架,可更轻松地构建IM服务器。
  • linkio  - 用于读取器/写入器接口的网络链接速度模拟。
  • llb  - 这是代理服务器的非常简单但快速的后端。对于零内存分配和快速响应的快速重定向到预定义域很有用。
  • mdns  - Golang中的简单mDNS(多播DNS)客户端/服务器库。
  • mqttPaho  - Paho Go客户端提供了一个MQTT客户端库,用于通过TCP,TLS或WebSockets连接到MQTT代理。
  • NFF-Go  - 用于快速开发云和裸机(以前的YANFF)的高性能网络功能的框架。
  • packet  - 通过TCP和UDP发送数据包。如果需要,它可以缓冲消息和热交换连接。
  • peerdiscovery  - Pure Go库,用于使用UDP多播的跨平台本地对等发现。
  • portproxy  - 简单的TCP代理,它将不支持它的API添加到CORS支持中。
  • publicip  - 软件包publicip返回您的面向公众的IPv4地址(互联网出口)。
  • quic-go  - 在纯Go中实现QUIC协议。
  • raw  - 包raw允许在设备驱动程序级别为网络接口读取和写入数据。
  • sftp  - 程序包sftp实现SSH文件传输协议,如https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
  • ssh  - 用于构建SSH服务器的高级API(包装crypto / ssh)。
  • sslb  - 这是一个超级简单的负载均衡器,只是一个实现某种性能的小项目。
  • stun  - 实施RFC 5389 STUN协议。
  • tcp_server  - 用于更快地构建tcp服务器的Go库。
  • tspool  - TCP库使用工作池来提高性能并保护您的服务器。
  • utp  - 围棋UTP微传输协议的实现。
  • water  - 简单的TUN / TAP库。
  • webrtc  - WebRTC API的纯Go实现。
  • winrm  - 进入WinRM客户端以在Windows计算机上远程执行命令。
  • xtcp  - 具有同步全双工通信,安全关闭,自定义协议的TCP Server Framework。

视频

  • go-astisub  - 在GO中处理字幕(.srt,.stl,.ttml,.webvtt,.ssa / .ass,图文电视,.smi等)。
  • go-astits  - 在GO中本地解析和解复用MPEG传输流(.ts)。
  • go-m3u8  - Apple m3u8播放列表的解析器和生成器库。
  • goav  - FFmpeg的综合Go绑定。
  • gst  - GStreamer的绑定。
  • libgosubs  - go的字幕格式支持。支持.srt,.ttml和.ass。
  • libvlc-go  - libvlc 2.X / 3.X / 4.X的绑定(由VLC媒体播放器使用)。
  • m3u8  - Apple HLS的M3U8播放列表的解析器和生成器库。
  • v4l  - 用Go编写的Linux视频捕获库。

开源书籍

视频网课

外链问题,到github项目里自取吧哈哈哈。
<!-- 关注公众号 回复【教程】即可在知识的海洋里呛水。 -->






查看原文

赞 2 收藏 2 评论 0

ilini 关注了用户 · 1月3日

华为云开发者社区 @huaweiyunkaifazheshequ

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算!

关注 265

ilini 发布了文章 · 2020-11-27

连json包变量不加tag会怎么样都不知道?年轻人耗子尾汁

问题

json包里使用的时候,结构体里的变量不加tag能不能正常转成json里的字段?

怎么答

  • 如果变量首字母小写,则为private。无论如何不能转,因为取不到反射信息
  • 如果变量首字母大写,则为public

    • 不加tag,可以正常转为json里的字段,json内字段名跟结构体内字段原名一致
    • 加了tag,从structjson的时候,json的字段名就是tag里的字段名,原字段名已经没用。

举例

通过一个例子加深理解。

package main
import (
    "encoding/json"
    "fmt"
)
type J struct {
    a string             //小写无tag
    b string `json:"B"`  //小写+tag
    C string             //大写无tag
    D string `json:"DD"` //大写+tag
}
func main() {
    j := J {
      a: "1",
      b: "2",
      C: "3",
      D: "4",
    }
    fmt.Printf("转为json前j结构体的内容 = %+v\n", j)
    jsonInfo, _ := json.Marshal(j)
    fmt.Printf("转为json后的内容 = %+v\n", string(jsonInfo))
}

输出

转为json前j结构体的内容 = {a:1 b:2 C:3 D:4}
转为json后的内容 = {"C":"3","DD":"4"}

解释

  • 结构体里定义了四个字段,分别对应 小写无tag小写+tag大写无tag大写+tag
  • 转为json后首字母小写的不管加不加tag都不能转为json里的内容,而大写的加了tag可以取别名,不加tagjson内的字段跟结构体字段原名一致

文章推荐:

如果你想每天学习一个知识点?

查看原文

赞 0 收藏 0 评论 0

ilini 发布了文章 · 2020-11-24

http/net源码阅读,goroutine泄漏个数分析

http/net源码阅读,goroutine泄漏个数分析

问题

package main

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

func main() {
    num := 6
    for index := 0; index < num; index++ {
        resp, _ := http.Get("https://www.baidu.com")
        _, _ = ioutil.ReadAll(resp.Body)
    }
    fmt.Printf("此时goroutine个数= %d\n", runtime.NumGoroutine())
}

上面这道题在不执行resp.Body.Close()的情况下,泄漏了吗?如果泄漏,泄漏了多少个goroutine?

怎么答

  • 不进行resp.Body.Close(),泄漏是一定的。但是泄漏的goroutine个数就让我迷糊了。由于执行了6遍,每次泄漏一个读和写goroutine,就是12个goroutine,加上main函数本身也是一个goroutine,所以答案是13.
  • 然而执行程序,发现答案是3,出入有点大,为什么呢?

解释

  • 我们直接看源码。golanghttp 包。
http.Get()

-- DefaultClient.Get
----func (c *Client) do(req *Request)
------func send(ireq *Request, rt RoundTripper, deadline time.Time)
-------- resp, didTimeout, err = send(req, c.transport(), deadline) 
// 以上代码在 go/1.12.7/libexec/src/net/http/client:174 

func (c *Client) transport() RoundTripper {
    if c.Transport != nil {
        return c.Transport
    }
    return DefaultTransport
}
  • 说明 http.Get 默认使用 DefaultTransport 管理连接。
DefaultTransport 是干嘛的呢?
// It establishes network connections as needed
// and caches them for reuse by subsequent calls.
  • DefaultTransport 的作用是根据需要建立网络连接并缓存它们以供后续调用重用。
那么 DefaultTransport 什么时候会建立连接呢?

接着上面的代码堆栈往下翻

func send(ireq *Request, rt RoundTripper, deadline time.Time) 
--resp, err = rt.RoundTrip(req) // 以上代码在 go/1.12.7/libexec/src/net/http/client:250
func (t *Transport) RoundTrip(req *http.Request)
func (t *Transport) roundTrip(req *Request)
func (t *Transport) getConn(treq *transportRequest, cm connectMethod)
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error) {
    ...
    go pconn.readLoop()  // 启动一个读goroutine
    go pconn.writeLoop() // 启动一个写goroutine
    return pconn, nil
}
  • 一次建立连接,就会启动一个读goroutine写goroutine。这就是为什么一次http.Get()会泄漏两个goroutine的来源。
  • 泄漏的来源知道了,也知道是因为没有执行close
那为什么不执行 close 会泄漏呢?
  • 回到刚刚启动的读goroutinereadLoop() 代码里
func (pc *persistConn) readLoop() {
    alive := true
    for alive {
        ...
        // Before looping back to the top of this function and peeking on
        // the bufio.Reader, wait for the caller goroutine to finish
        // reading the response body. (or for cancelation or death)
        select {
        case bodyEOF := <-waitForBodyRead:
            pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool
            alive = alive &&
                bodyEOF &&
                !pc.sawEOF &&
                pc.wroteRequest() &&
                tryPutIdleConn(trace)
            if bodyEOF {
                eofc <- struct{}{}
            }
        case <-rc.req.Cancel:
            alive = false
            pc.t.CancelRequest(rc.req)
        case <-rc.req.Context().Done():
            alive = false
            pc.t.cancelRequest(rc.req, rc.req.Context().Err())
        case <-pc.closech:
            alive = false
        }
        ...
    }
}
  • 简单来说readLoop就是一个死循环,只要alivetruegoroutine就会一直存在
  • select 里是 goroutine有可能退出的场景:

    • body 被读取完毕或body关闭
    • request 主动 cancel
    • requestcontext Done 状态 true
    • 当前的 persistConn 关闭

其中第一个 body 被读取完或关闭这个 case:

alive = alive &&
    bodyEOF &&
    !pc.sawEOF &&
    pc.wroteRequest() &&
    tryPutIdleConn(trace)

bodyEOF 来源于到一个通道 waitForBodyRead,这个字段的 truefalse 直接决定了 alive 变量的值(alive=true读goroutine继续活着,循环,否则退出goroutine)。

那么这个通道的值是从哪里过来的呢?
// go/1.12.7/libexec/src/net/http/transport.go: 1758
        body := &bodyEOFSignal{
            body: resp.Body,
            earlyCloseFn: func() error {
                waitForBodyRead <- false
                <-eofc // will be closed by deferred call at the end of the function
                return nil

            },
            fn: func(err error) error {
                isEOF := err == io.EOF
                waitForBodyRead <- isEOF
                if isEOF {
                    <-eofc // see comment above eofc declaration
                } else if err != nil {
                    if cerr := pc.canceled(); cerr != nil {
                        return cerr
                    }
                }
                return err
            },
        }
  • 如果执行 earlyCloseFnwaitForBodyRead 通道输入的是 falsealive 也会是 false,那 readLoop() 这个 goroutine 就会退出。
  • 如果执行 fn ,其中包括正常情况下 body 读完数据抛出 io.EOF 时的 casewaitForBodyRead 通道输入的是 true,那 alive 会是 true,那么 readLoop() 这个 goroutine 就不会退出,同时还顺便执行了 tryPutIdleConn(trace)
// tryPutIdleConn adds pconn to the list of idle persistent connections awaiting
// a new request.
// If pconn is no longer needed or not in a good state, tryPutIdleConn returns
// an error explaining why it wasn't registered.
// tryPutIdleConn does not close pconn. Use putOrCloseIdleConn instead for that.
func (t *Transport) tryPutIdleConn(pconn *persistConn) error
  • tryPutIdleConnpconn 添加到等待新请求的空闲持久连接列表中,也就是之前说的连接会复用。
那么问题又来了,什么时候会执行这个 fnearlyCloseFn 呢?
func (es *bodyEOFSignal) Close() error {
    es.mu.Lock()
    defer es.mu.Unlock()
    if es.closed {
        return nil
    }
    es.closed = true
    if es.earlyCloseFn != nil && es.rerr != io.EOF {
        return es.earlyCloseFn() // 关闭时执行 earlyCloseFn
    }
    err := es.body.Close()
    return es.condfn(err)
}
  • 上面这个其实就是我们比较收悉的 resp.Body.Close() ,在里面会执行 earlyCloseFn,也就是此时 readLoop() 里的 waitForBodyRead 通道输入的是 falsealive 也会是 false,那 readLoop() 这个 goroutine 就会退出,goroutine 不会泄露。
b, err = ioutil.ReadAll(resp.Body)
--func ReadAll(r io.Reader) 
----func readAll(r io.Reader, capacity int64) 
------func (b *Buffer) ReadFrom(r io.Reader)


// go/1.12.7/libexec/src/bytes/buffer.go:207
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
    for {
        ...
        m, e := r.Read(b.buf[i:cap(b.buf)])  // 看这里,是body在执行read方法
        ...
    }
}
  • 这个read,其实就是 bodyEOFSignal 里的
func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
    ...
    n, err = es.body.Read(p)
    if err != nil {
        ... 
    // 这里会有一个io.EOF的报错,意思是读完了
        err = es.condfn(err)
    }
    return
}


func (es *bodyEOFSignal) condfn(err error) error {
    if es.fn == nil {
        return err
    }
    err = es.fn(err)  // 这了执行了 fn
    es.fn = nil
    return err
}
  • 上面这个其实就是我们比较收悉的读取 body 里的内容。 ioutil.ReadAll() ,在读完 body 的内容时会执行 fn,也就是此时 readLoop() 里的 waitForBodyRead 通道输入的是 truealive 也会是 true,那 readLoop() 这个 goroutine 就不会退出,goroutine 会泄露,然后执行 tryPutIdleConn(trace) 把连接放回池子里复用。

总结

  • 所以结论呼之欲出了,虽然执行了 6 次循环,而且每次都没有执行 Body.Close() ,就是因为执行了ioutil.ReadAll()把内容都读出来了,连接得以复用,因此只泄漏了一个读goroutine和一个写goroutine,最后加上main goroutine,所以答案就是3个goroutine
  • 从另外一个角度说,正常情况下我们的代码都会执行 ioutil.ReadAll(),但如果此时忘了 resp.Body.Close(),确实会导致泄漏。但如果你调用的域名一直是同一个的话,那么只会泄漏一个 读goroutine 和一个写goroutine这就是为什么代码明明不规范但却看不到明显内存泄漏的原因
  • 那么问题又来了,为什么上面要特意强调是同一个域名呢?改天,回头,以后有空再说吧。

文章推荐:

如果你想每天学习一个知识点?

查看原文

赞 1 收藏 0 评论 1

ilini 赞了文章 · 2020-10-26

Socket之so_linger与rst

看Apache HttpClient的源码时,发现abortRequest的时候,调用到socket时代码如下:

public void shutdown() throws IOException {
        Socket socket = (Socket)this.socketHolder.getAndSet((Object)null);
        if (socket != null) {
            try {
                socket.setSoLinger(true, 0);
            } catch (IOException var6) {
                ;
            } finally {
                socket.close();
            }
        }

    }

what is so_linger?这个嘛得先从tcp三次握手与四次挥手说起:

TCP三次握手与四次挥手

三次握手建立连接:
clipboard.png
为了能够说清楚后面的RST攻击,需要结合上图说说:SYN标志位、序号、滑动窗口大小。
建立连接的请求中,标志位SYN都要置为1,在这种请求中会告知MSS段大小,就是本机希望接收TCP包的最大大小。
发送的数据TCP包都有一个序号。它是这么得来的:最初发送SYN时,有一个初始序号,根据RFC的定义,各个操作系统的实现都是与系统时间相关的。之后,序号的值会不断的增加,比如原来的序号是100,如果这个TCP包的数据有10个字节,那么下次的TCP包序号会变成110。
滑动窗口用于加速传输,比如发了一个seq=100的包,理应收到这个包的确认ack=101后再继续发下一个包,但有了滑动窗口,只要新包的seq与没有得到确认的最小seq之差小于滑动窗口大小,就可以继续发。

滑动窗口
滑动窗口毫无疑问是用来加速数据传输的。TCP要保证“可靠”,就需要对一个数据包进行ack确认表示接收端收到。有了滑动窗口,接收端就可以等收到许多包后只发一个ack包,确认之前已经收到过的多个数据包。有了滑动窗口,发送端在发送完一个数据包后不用等待它的ack,在滑动窗口大小内可以继续发送其他数据包。
如果谈到TCP攻击就需要注意,TCP的各种实现中,在滑动窗口之外的seq会被扔掉!

四次握手的正常TCP连接关闭

clipboard.png
FIN标志位也看到了,它用来表示正常关闭连接。图的左边是主动关闭连接方,右边是被动关闭连接方,用netstat命令可以看到标出的连接状态。
FIN是正常关闭,它会根据缓冲区的顺序来发的,就是说缓冲区FIN之前的包都发出去后再发FIN包,这与RST不同。

RST标志位

RST表示复位,用来异常的关闭连接,在TCP的设计中它是不可或缺的。就像上面说的一样,发送RST包关闭连接时,不必等缓冲区的包都发出去(不像上面的FIN包),直接就丢弃缓存区的包发送RST包。而接收端收到RST包后,也不必发送ACK包来确认。
TCP处理程序会在自己认为的异常时刻发送RST包。例如,A向B发起连接,但B之上并未监听相应的端口,这时B操作系统上的TCP处理程序会发RST包。
又比如,AB正常建立连接了,正在通讯时,A向B发送了FIN包要求关连接,B发送ACK后,网断了,A通过若干原因放弃了这个连接(例如进程重启)。网通了后,B又开始发数据包,A收到后表示压力很大,不知道这野连接哪来的,就发了个RST包强制把连接关了,B收到后会出现connect reset by peer错误。

说了这么多好像还没说so_linger.

so_linger

Linux网络编程中,socket的选项很多.其中几个比较重要的选项有:SO_LINGER(仅仅适用于TCP,SCTP), SO_REUSEADDR.
图片描述
在默认情况下,当调用close关闭socke的使用,close会立即返回,但是,如果send buffer中还有数据,系统会试着先把send buffer中的数据发送出去,然后close才返回.

SO_LINGER选项则是用来修改这种默认操作的.于SO_LINGER相关联的一个结构体如下:

#include <sys/socket.h> 
struct linger { 
int l_onoff //0=off, nonzero=on(开关) 
int l_linger //linger time(延迟时间) 
} 

当调用setsockopt之后,该选项产生的影响取决于linger结构体中 l_onoff和l_linger的值:

0 = l_onoff
当l_onoff被设置为0的时候,将会关闭SO_LINGER选项,即TCP或则SCTP保持默认操作:close立即返回.l_linger值被忽略.

l_lineoff值非0,0 = l_linger
当调用close的时候,TCP连接会立即断开.send buffer中未被发送的数据将被丢弃,并向对方发送一个RST信息.值得注意的是,由于这种方式,是非正常的4中握手方式结束TCP链接,所以,TCP连接将不会进入TIME_WAIT状态,这样会导致新建立的可能和就连接的数据造成混乱。

l_onoff和l_linger都是非0
在这种情况下,回事的close返回得到延迟。调用close去关闭socket的时候,内核将会延迟。也就是说,如果send buffer中还有数据尚未发送,该进程将会被休眠直到一下任何一种情况发生:
1) send buffer中的所有数据都被发送并且得到对方TCP的应答消息(这种应答并不是意味着对方应用程序已经接收到数据,在后面shutdown将会具体讲道)
2) 延迟时间消耗完。在延迟时间被消耗完之后,send buffer中的所有数据都将会被丢弃。

上面1),2)两种情况中,如果socket被设置为O_NONBLOCK状态,程序将不会等待close返回,send buffer中的所有数据都将会被丢弃。所以,需要我们判断close的返回值。在send buffer中的所有数据都被发送之前并且延迟时间没有消耗完,close返回的话,close将会返回一个EWOULDBLOCK的error.

1.默认操作的close,close立即返回

clipboard.png
说明:我们已经知道write操作返回成功只能说明数据已经发送到套接字的发送缓冲区,不能代表对端已经成功收到数据,close的默认返回成功也只是成功发出了一个FIN分节,也不代表对端已经确认

2.设置SO_LINGER套接字选项且l_linger为正值时的close:【数据ack了,并收到FIN了】 才返回

clipboard.png
说明:这种情况下客户的close要到它的数据和FIN已经被服务器的TCP确认以后才会返回;

3.设置SO_LINGER套接字选项且l_linger为偏小正值时的close:时间到了 返回-1,EWOULDBLOCK错误

clipboard.png

说明:在服务端的确认到达之前,SO_LINGER套接字选项设置的延滞时间到,close将会返回EWOULDBLOCK错误,且套接字发送缓冲区中的任何残留数据被丢弃。

总结:设置SO_LINGER套接字选项以后,close的成功返回只是告诉我们先前发送的数据的FIN已经由对端TCP确认,而不能告诉我们对端应用进程是否已经读取数据,如果不设置该套接字选项,那么我们连对端TCP是否确认了数据都不知道。

l_onoff 非0,l_linger为0,丢弃发送缓冲区的任何数据,并发送rst给对端,没有四分组连接终止序列--没有fin,这样一来避免了TIME_WAIT状态,但是在2MSL秒内创建该连接的新化身,导致来自刚刚终止的连接上的旧数据被不正确的传送到新的化身上

回到这段代码

public void shutdown() throws IOException {
        Socket socket = (Socket)this.socketHolder.getAndSet((Object)null);
        if (socket != null) {
            try {
                socket.setSoLinger(true, 0);
            } catch (IOException var6) {
                ;
            } finally {
                socket.close();
            }
        }

    }

设置的是上述第三点:设置SO_LINGER套接字选项且l_linger为偏小正值时的close:时间到了 返回-1,EWOULDBLOCK错误. 为0立即返回,然后收到FIN之后发送RST,不会进入TIME_WAIT状态。

附:RST攻击

A和服务器B之间建立了TCP连接,此时C伪造了一个TCP包发给B,使B异常的断开了与A之间的TCP连接,就是RST攻击了。实际上从上面RST标志位的功能已经可以看出这种攻击如何达到效果了。
那么伪造什么样的TCP包可以达成目的呢?我们至顶向下的看。
假定C伪装成A发过去的包,这个包如果是RST包的话,毫无疑问,B将会丢弃与A的缓冲区上所有数据,强制关掉连接。
如果发过去的包是SYN包,那么,B会表示A已经发疯了(与OS的实现有关),正常连接时又来建新连接,B主动向A发个RST包,并在自己这端强制关掉连接。

这两种方式都能够达到复位攻击的效果。似乎挺恐怖,然而关键是,如何能伪造成A发给B的包呢?这里有两个关键因素,源端口和序列号。
一个TCP连接都是四元组,由源IP、源端口、目标IP、目标端口唯一确定一个连接。所以,如果C要伪造A发给B的包,要在上面提到的IP头和TCP头,把源IP、源端口、目标IP、目标端口都填对。这里B作为服务器,IP和端口是公开的,A是我们要下手的目标,IP当然知道,但A的源端口就不清楚了,因为这可能是A随机生成的。当然,如果能够对常见的OS如windows和linux找出生成source port规律的话,还是可以搞定的。
序列号问题是与滑动窗口对应的,伪造的TCP包里需要填序列号,如果序列号的值不在A之前向B发送时B的滑动窗口内,B是会主动丢弃的。所以我们要找到能落到当时的AB间滑动窗口的序列号。这个可以暴力解决,因为一个sequence长度是32位,取值范围0-4294967296,如果窗口大小像上图中我抓到的windows下的65535的话,只需要相除,就知道最多只需要发65537(4294967296/65535=65537)个包就能有一个序列号落到滑动窗口内。RST包是很小的,IP头+TCP头也才40字节,算算我们的带宽就知道这实在只需要几秒钟就能搞定。

那么,序列号不是问题,源端口会麻烦点,如果各个操作系统不能完全随机的生成源端口,或者黑客们能通过其他方式获取到source port,RST攻击易如反掌,后果很严重。

参考:

http://blog.csdn.net/russell_...
https://www.cnblogs.com/my_li...
http://blog.csdn.net/le119126...
《UNIX Network ProgrammingVolume 1, Third Edition: TheSockets Networking API》
http://blog.csdn.net/yusiguyu...
http://blog.csdn.net/yusiguyu...
http://www.cs.northwestern.ed...

查看原文

赞 1 收藏 1 评论 0

ilini 发布了文章 · 2020-10-15

连nil切片和空切片一不一样都不清楚?那BAT面试官只好让你回去等通知了

连nil切片和空切片一不一样都不清楚?那BAT面试官只好让你回去等通知了。

问题

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {

    var s1 []int   // nil切片
    s2 := make([]int,0)  // 空切片
    s4 := make([]int,0)   // 空切片
    
    fmt.Printf("s1 pointer:%+v, s2 pointer:%+v, s4 pointer:%+v, \n", *(*reflect.SliceHeader)(unsafe.Pointer(&s1)),*(*reflect.SliceHeader)(unsafe.Pointer(&s2)),*(*reflect.SliceHeader)(unsafe.Pointer(&s4)))
    fmt.Printf("%v\n", (*(*reflect.SliceHeader)(unsafe.Pointer(&s1))).Data==(*(*reflect.SliceHeader)(unsafe.Pointer(&s2))).Data)
    fmt.Printf("%v\n", (*(*reflect.SliceHeader)(unsafe.Pointer(&s2))).Data==(*(*reflect.SliceHeader)(unsafe.Pointer(&s4))).Data)
}

nil切片和空切片指向的地址一样吗?这个代码会输出什么?

怎么答

  • nil切片和空切片指向的地址不一样。nil空切片引用数组指针地址为0(无指向任何实际地址)
  • 空切片的引用数组指针地址是有的,且固定为一个值
s1 pointer:{Data:0 Len:0 Cap:0}, s2 pointer:{Data:824634207952 Len:0 Cap:0}, s4 pointer:{Data:824634207952 Len:0 Cap:0}, 
false //nil切片和空切片指向的数组地址不一样
true  //两个空切片指向的数组地址是一样的,都是824634207952

解释

type SliceHeader struct {
 Data uintptr  //引用数组指针地址
 Len  int     // 切片的目前使用长度
 Cap  int     // 切片的容量
}
  • nil切片和空切片最大的区别在于指向的数组引用地址是不一样的

nil切片和空切片的区别

  • 所有的空切片指向的数组引用地址都是一样的

空切片都指向同一个地址

文章推荐:

如果你想每天学习一个知识点?

查看原文

赞 0 收藏 0 评论 0

ilini 发布了文章 · 2020-09-25

不要等离职了,才知道for select时,如果通道已经关闭会怎么样?

golang面试官:for select时,如果通道已经关闭会怎么样?如果select中只有一个case呢?

问题

for循环select时,如果通道已经关闭会怎么样?如果select中的case只有一个,又会怎么样?

怎么答

  • for循环select时,如果其中一个case通道已经关闭,则每次都会执行到这个case。
  • 如果select里边只有一个case,而这个case被关闭了,则会出现死循环。

解释

1.for循环里被关闭的通道

    • c通道是一个缓冲为0的通道,在main开始时,启动一个协程对c通道写入10,然后就关闭掉这个通道。
    • main中通过 x, ok := <-c 接受通道c里的值,从输出结果里看出,确实从通道里读出了之前塞入通道的10,但是在通道关闭后,这个通道一直能读出内容。

    2.怎么样才能不读关闭后通道

    • x, ok := <-c 返回的值里第一个x是通道内的值,ok是指通道是否关闭,当通道被关闭后,ok则返回false,因此可以根据这个进行操作。读一个已经关闭的通道为什么会出现false,可以看我之前的 对已经关闭的的chan进行读写,会怎么样?为什么?
    • 当返回的okfalse时,执行c = nil 将通道置为nil,相当于读一个未初始化的通道,则会一直阻塞。至于为什么读一个未初始化的通道会出现阻塞,可以看我的另一篇 对未初始化的的chan进行读写,会怎么样?为什么?select中如果任意某个通道有值可读时,它就会被执行,其他被忽略。则select会跳过这个阻塞case,可以解决不断读已关闭通道的问题。

    3.如果select里只有一个已经关闭的case,会怎么样?

    • 可以看出只有一个case的情况下,则会死循环
    • 那如果像上面一个case那样,把通道置为nil就能解决问题了吗?

    4.select里只有一个已经关闭的case,置为nil,会怎么样?

    • 第一次读取case能读到通道里的10
    • 第二次读取case能读到通道已经关闭的信息。此时将通道置为nil
    • 第三次读取case时main协程会被阻塞,此时整个进程没有其他活动的协程了,进程deadlock

    总结

    • select中如果任意某个通道有值可读时,它就会被执行,其他被忽略。
    • 如果没有default字句,select将有可能阻塞,直到某个通道有值可以运行,所以select里最好有一个default,否则将有一直阻塞的风险。

    文章推荐:

    如果你想每天学习一个知识点?

    查看原文

    赞 3 收藏 0 评论 0

    ilini 发布了文章 · 2020-09-24

    连怎么避免内存逃逸都不知道?怎么进BAT?

    问题

    怎么避免内存逃逸

    怎么答

    runtime/stubs.go:133有个函数叫noescapenoescape可以在逃逸分析中隐藏一个指针。让这个指针在逃逸分析中不会被检测为逃逸

    举例

    • 通过一个例子加深理解,接下来尝试下怎么通过 go build -gcflags=-m 查看逃逸的情况。

    执行go build -gcflags=-m main.go

    其中主要看中间一小段

    解释

    • 上段代码对AATrick同样的功能有两种实现:他们包含一个 string ,然后用 String() 方法返回这个字符串。但是从逃逸分析看ATrick 版本没有逃逸。
    • noescape() 函数的作用是遮蔽输入和输出的依赖关系。使编译器不认为 p 会通过 x 逃逸, 因为 uintptr() 产生的引用是编译器无法理解的。
    • 内置的 uintptr 类型是一个真正的指针类型,但是在编译器层面,它只是一个存储一个 指针地址int 类型。代码的最后一行返回 unsafe.Pointer 也是一个 int
    • noescape()runtime 包中使用 unsafe.Pointer 的地方被大量使用。如果作者清楚被 unsafe.Pointer 引用的数据肯定不会被逃逸,但编译器却不知道的情况下,这是很有用的。
    • 面试中秀一秀是可以的,如果在实际项目中如果使用这种unsafe包大概率会被同事打死。不建议使用! 毕竟包的名字就叫做 unsafe, 而且源码中的注释也写明了 USE CAREFULLY!

    文章推荐:

    如果你想每天学习一个知识点?

    查看原文

    赞 0 收藏 0 评论 0

    认证与成就

    • 获得 17 次点赞
    • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

    擅长技能
    编辑

    (゚∀゚ )
    暂时没有

    开源项目 & 著作
    编辑

    (゚∀゚ )
    暂时没有

    注册于 2017-03-29
    个人主页被 1.4k 人浏览