网络编程

HTTP 请求

制作一个 http 请求需要用到 http 包,一个简单的示例如下:

request, err := http.NewRequestWithContext(context.Background(), "GET", repo, nil)
if err != nil {
        panic(err.Error())
}

request.Header.Add("User-Agent", `'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66'`)

response, err := http.DefaultClient.Do(request)

if err != nil {
        panic(err.Error())
}

defer response.Body.Close()

if response.StatusCode != 200 {
        panic(err.Error())
}

text, err := ioutil.ReadAll(response.Body)

Json

Go 使用 json.Unmarshal 将 Json 数据映射到结构体上:

var data []ReleaseData

err = json.Unmarshal(jsonData, &data)

if err != nil {
        panic(err.Error())
}

for i := 0; i < len(data); i++ {
        release := data[i]
        assets := release.Assets

        for j := 0; j < len(assets); j++ {
                asset := assets[j]
                str := fmt.Sprintf("%v %v %v %v", release.Name, release.Date, asset.Name, asset.Url)
                fmt.Println(str)
        }
}

其中结构体的定义为:

type ReleaseAsset struct {
   Name string `json:"name"`
   URL  string `json:"browser_download_url"`
}

type ReleaseData struct {
   Name   string         `json:"tag_name"`
   Date   string         `json:"published_at"`
   Assets []ReleaseAsset `json:"assets"`
}

结构体的数据成员的名字首字母需要大写

HTML 解析

一个简单的示例为:

doc, err := goquery.NewDocumentFromReader(response.Body)
if err != nil {
        panic(err.Error())
}

doc.Find("tr>td>.comma-separated").First().Find("li").Each(func(i int, s *goquery.Selection) {
        Output(url, s.Text())
})

限制连接数量

Linux 对于程序允许打开的文件数量有限制,此限制可以通过 cat /proc/pid/limits | grep files | awk '{print $4}' 查看,而程序已经打开的文件数量则可以通过 ls /dev/fd/ | wc -l 查看

程序打开的套接字数量应当总是小于可用套接字数量

另外,go 会对套接字进行缓存,因此还需要限制连接池中可缓存的连接数量。

下面的代码可用于查询可用套接字数量,并设置缓存套接字数量。之所以 fileLimit 需要减二,是因为

  • 下标是从 0 开始的

  • 信号量在小于零时才会阻塞

  var (
          fileLimit = 1024
          cmd       = fmt.Sprintf(`cat /proc/%v/limits | grep files | awk '{print $4}'`, os.Getpid())
          cmder     = exec.Command("/bin/bash", "-c", cmd)
  )

  if data, err := cmder.CombinedOutput(); err == nil {
          fileLimit, _ = strconv.Atoi(strings.ReplaceAll(string(data), "\n", ""))
  }

  cmder = exec.Command("/bin/bash", "-c", "ls /dev/fd/| wc -l")

  if data, err := cmder.CombinedOutput(); err == nil {
          if openedFiles, err := strconv.Atoi(strings.ReplaceAll(string(data), "\n", "")); err == nil {
                  fileLimit -= openedFiles
          }
  }

  fileLimit -= 2
  sem = lib.NewSemaphore(fileLimit)

  if transport, ok := http.DefaultTransport.(*http.Transport); ok {
          transport.MaxIdleConns = fileLimit
transport.MaxIdleConnsPerHost = 0
transport.MaxConnsPerHost = fileLimit
transport.DisableKeepAlives = true

  }