Go基础:了解 HTTP Client, HTTP Server, Context, Processes, Signals, Exit in Go

讨论网络概念、HTTP通信和进程管理。

full lessons here👇:
https://programmerscareer.com/zh-cn/golang-basic-skill/

由AI生成,可能有错误,仅供参考

Topic:0.1基本网络理解

从基础开始学习通常会使学习过程更加有效率。即使你已经熟悉相关主题,快速复习也可以帮助巩固你的理解。

计算机网络由多个电脑组成,这些电脑彼此连接,以共享资源和数据为目的。

常见的计算机网络类型包括:

  • 局域网(LAN):一个小范围内的网络。通常限于一个单独的建筑物,例如家、办公室或一组建筑物。
  • 广域网(WAN):跨越大物理距离的一种网络。互联网是一个 WAN。

基本网络协议理解也非常重要。协议是一组规则,它 dictate 数据如何在网络上传输。

Internet Protocol(IP)、Transmission Control Protocol(TCP)和 User Datagram Protocol(UDP)是你必须了解的基本协议。

在 Go 中,‘net’ 包提供了一般网络 I/O 接口,包括 TCP/IP、UDP、域名解析和 Unix 域套接字。

这些网络概念将成为我们深入探讨 Go 处理 HTTP 通信和管理进程时的基础。

Topic:0.2 Go 的 net 包介绍

Go 的标准库提供了 HTTP 的优秀支持。‘net/http’ 包提供了构建 HTTP 客户端和服务器的功能。但是在 dive 到 HTTP 之前,我们需要了解 ‘net’ 包。

‘net’ 包在 Go 中提供了一般网络 I/O 接口,包括 TCP/IP、UDP、域名解析和 Unix 域套接字。

让我们快速浏览一下 ‘net’ 包中的一些函数,这些函数我们将使用:

  1. Dial: Dial 函数连接到指定网络上的地址。
1
2
3
4
conn, err := net.Dial("tcp", "golang.org:80")
if err != nil {
log.Fatal(err)
}
  1. Listen: Listen 声明在本地网络上。您将使用这个来创建一个服务器,监听 incoming 连接。
1
2
3
4
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
  1. Accept: 并且这是如何等待和返回下一个连接到监听器的。
1
2
3
4
5
conn, err := ln.Accept()
if err != nil {
log.Fatal(err)
}
// 处理连接

不要担心如果第一次看起来很混乱。在下一节中,我们将深入探讨这些函数,并在代码和示例中实际使用它们。

Topic:0.3 HTTP 介绍

HTTP 是 Hypertext Transfer Protocol 的缩写,这是 Web 上数据交换的基础。HTTP 是全球范围内用于传输超文本的协议。

HTTP 的关键特点包括:

  1. 无状态性: HTTP 无状态,意味着每个客户端请求都是独立的。服务器和客户端不保留数据之间的数据。
  2. 媒体独立性: 只要客户端可以解释数据,就可以发送任何类型的媒体文件。

HTTP 作为客户端和服务器之间的一个请求-响应协议,包括浏览器、移动应用程序和其他服务器是 HTTP 客户端的一些例子。每当一个 HTTP 请求被客户端提出时,服务器将以 HTTP 响应回应。

在 Go 中,创建一个 HTTP 服务器变得轻松,因为标准 ‘net/http’ 包提供了 ListenAndServe 函数。

我们也可以使用 ‘http.Client’、‘http.NewRequest’ 和 ‘http.Do’ 命令来发送 HTTP 请求。

Go 提供了一种非常强大的 http 包,它提供了所有必要的工具来运行 HTTP 服务器或发送客户端 HTTP 请求。我们将在下一节中探讨 Go 的 ‘http’ 包,并实际使用代码和示例。

1.1 HTTP Client

Go 的 net/http 包提供了发送 HTTP 请求的方法。在本节中,我们将介绍发送 GET 请求的基本知识,这是 HTTP 请求的一种常见类型。

以下是一个基本示例,演示如何发送 GET 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

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

func main() {
resp, err := http.Get("http://example.com/")
if err != nil {
fmt.Printf("Error making the request: %v", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading the response: %v", err)
return
}
fmt.Println(string(body))
}

在这个示例中,我们使用 http.Get 函数向提供的 URL 发送 GET 请求,并返回响应和错误。

resp.Body 中包含服务器对请求的响应。ioutil.ReadAll 函数从响应体中读取所有数据。然后,我们将响应体打印到控制台。

defer resp.Body.Close() 行是关键的一部分。这确保了网络连接在函数完成后关闭,而不是立即关闭。这是一种良好的实践,因为重复使用连接池可以提高性能。

我希望您对我们的 HTTP 客户端课程的开始感到有帮助。请记住,实践是有益处的,因此请尝试编写一个 GET 请求以熟悉过程。

1.2 HTTP 服务器

在 Go 中,我们可以使用 net/http 包构建一个强大的 web 服务器。我们可以创建 RESTful 服务、静态文件服务、动态路由等。为了这个课程的 sake,让我们专注于创建一个简单的 web 服务器。

以下是一个基本示例,演示如何创建一个简单的 web 服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to Mr. Ranedeer's Go Tutorial, %s!", r.URL.Path[1:])
}

func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}

在这个示例中,我们使用 http.HandleFunc 函数将请求路由到处理函数中,然后使用 http.ListenAndServe 函数监听指定端口直到程序被终止。

实际上,服务器通常不用于发送“Hello, World”消息,而是用于发送动态内容,这些内容根据特定的情况或参数而变化。

为了illustrate 这,我们将查看一个简单的示例,演示如何处理 HTTP POST 请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Global variable to simulate a database.
var database []string

func formHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
html := `<form method="post" action="/submit">
Name: <input type="text" name="name">
<input type="submit" value="Submit">
</form>`
w.Write([]byte(html))
case "POST":
r.ParseForm()
// Logic part to store into database
database = append(database, r.Form.Get("name"))
}
}

func submitHandler(w http.ResponseWriter, r *http.Request) {
html := "Received POST data: <br/><br/>"
html += "<ul style='list-style: none'>"
for _, item := range database {
html += "<li>"
html += item
html += "</li><br/>"
}
html += "</ul>"
w.Write([]byte(html))
}

func main() {
server := http.Server{
Addr: ":8080",
}
http.HandleFunc("/form", formHandler)
http.HandleFunc("/submit", submitHandler)
server.ListenAndServe()
}

代码创建一个表单,询问名称。当您提交表单时,它将向 /submit 发送 POST 请求。这请求处理程序从 POST 数据中检索名称并存储。

1.3 Go 中的 Context 包

Go 中的 Context 包通常用于处理可能需要取消或超时的任务。并发编程在 Go 中的一个常见主题是管理长期和短期的 worker 任务。在一些场景中,例如处理数据流或自动化任务,这些任务可能会持续很久甚至 indefinitely。

Context 包提供了一种标准化的接口来定义父-子关系之间的关系,并传播截止日期、取消信号和其他信息,以便有效地处理任务。

让我们看看如何使用 Context:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"context"
"time"
)

func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
go func(ctx context.Context) {
for {
select {
case <-time.After(1 * time.Second):
fmt.Println("working")
case <-ctx.Done():
fmt.Println("canceled")
return
}
}
}(ctx)
time.Sleep(4 * time.Second)
}

在这个示例中,我们初始化了一个新的背景上下文 ctx,然后创建了一个自动取消的上下文使用 context.WithTimeout(ctx, 3*time.Second)。cancel 函数由 context.WithTimeout 返回,并使用 defer 来确保资源被正确地清理。

1.4 Go 中的进程

在计算世界中,一进程是一个正在执行的计算机程序实例。它包含了程序代码和当前活动。在 Go 中,可以使用 os/exec 包来启动一个进程。

以下是一个简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"os/exec"
)

func main() {
cmd := exec.Command("ls")
output, err := cmd.Output()
if err != nil {
fmt.Println("An error occurred:", err)
}
fmt.Println(string(output))
}

在这个示例中:

  • exec.Command("ls") 准备了一条命令来执行。这就像在终端中输入 ls 命令,但是它还没有真正地运行。
  • cmd.Output() 实际上运行了命令,并等待它完成。它返回标准输出和一个 Go 的内置接口类型 error

这个程序将打印当前目录的内容,类似于 Unix-based 系统中的 ls 命令。

Go 提供了一组强大的工具来管理进程。在简单的术语中,进程管理在任何语言中都涉及到跟踪和控制我们启动的进程。这就是关于知道程序的状态。

Topic 1.5: Go 中的信号

在操作系统中,信号是一种软件中断,它将被送到进程。进程可以使用这些信号来触发某些行为。例如,当程序在终端窗口中运行时,按下 ctrl+c 将发送一个中断信号,通常会导致程序终止。

在 Go 中,我们可以使用 os/signal 包来决定当我们的程序接收到信号时该做什么。这可以允许我们优雅地停止一个程序,并执行任何可能需要的清理操作。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"os"
"os/signal"
"syscall"
)

func main() {
signals := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-signals
fmt.Println()
fmt.Println(sig)
done <- true
}()
fmt.Println("waiting for signal")
<-done
fmt.Println("exiting")
}

在这个程序中

  • 我们首先创建一个信号通道。
  • 使用 signal.Notify 将 incoming signals relay 到通道中。我们特别关心 SIGINT(由按下 ctrl+c 发送)和 SIGTERM(对程序的终止请求)。
  • 当接收到信号时,它将被打印出来,然后“true”将被发送到 done 通道,以表示我们准备退出。

这样,我们可以优雅地处理中断,通过保存进度、释放资源、完成重要操作或询问用户确认前退出。

Topic 1.6: Go 中的干净退出

在任何结构良好的程序中总是需要释放程序消耗的资源,这些资源可能包括打开的文件、数据库连接、网络连接或分配的内存。Go 提供了 defer 语句,帮助我们自动管理这些资源。

defer 语句的关键思想

defer 语句允许我们延迟执行一个函数直到包含它的函数返回,无论如何返回。

考虑以下常见情况

我们打开一个文件,执行一些操作,然后关闭它:

1
2
3
4
5
6
func main() {
f, _ := os.Open("myfile.txt")
// Perform operations on the file
// …
f.Close()
}

现在,让我们假设在文件操作中出现错误,我们从函数返回。这样,Close 函数将永远不会被调用,从而导致文件描述符泄露。这是 defer 可以发挥作用的地方:

1
2
3
4
5
6
7
func main() {
f, _ := os.Open("myfile.txt")
defer f.Close()
// Perform operations on the file
// …
// 文件现在将自动关闭
}

在修订版本中

f.Close() 将在每次函数返回时执行,无论如何返回。这确保我们不会留下任何资源悬挂。

1.7 Review and Practice

让我们快速回顾一下:

  • HTTP Client:我们学习了使用 Go 的标准库 net/http 发送 HTTP 请求,并了解如何获取和发送数据。
  • HTTP Server:我们探索了设计 HTTP 服务器并处理 incoming 请求的方法。
  • Context in Go:我们了解了使用 context 包来管理长时间运行的进程。
  • Processes in Go:我们学习了在 Go 中创建和管理进程的方法。
  • Signals in Go:我们探索了如何处理系统信号以实现应用程序的优雅关闭。
  • Clean Exit in Go:我们学习了资源清理在应用程序终止前,并且您了解了 defer 关键字。

现在,让我们将这些知识付诸实践,完成以下练习:

  1. 在 Go 中创建一个监听 8080 端口的 HTTP 服务器,并响应 /ping 路由返回 Pong!
  2. 在 Go 中创建一个发送 GET 请求到 API 端口的 HTTP 客户端。
  3. 编写一个无限循环的 Go 函数,但在接收到关闭信号 (SIGINTSIGTERM)时优雅地退出。
  4. 开发一个简单的 Go 脚本,打开文件、写入内容,并确保文件即使在写入操作中出现错误也能被正确关闭。

如果您在解决这些练习或需要澄清我们所学的知识,请不要犹豫地问。

1.8 Assessments

了解自己的情况是改进的关键。为此,我们准备了一组独特的问题让您回答。一些可能会很难,但我们确信您可以处理它们。

问题:

  1. 请编写一个 REST API 在 Go 中。这 API 包括一个获取用户数据的 GET 端口。描述如何设计这个端口。
  2. 如何在 Go 中处理长时间运行的 HTTP 请求超时?
  3. 假设您的 Go 应用程序中有一个需要在某些条件下停止的长时间 Goroutine。如何设计代码以优雅地停止该 Goroutine?
  4. 如果您的 Go 应用程序接收到 SIGKILL 信号,可以您处理这种信号吗?
  5. 您的 Go 应用程序打开了一个文件。但是由于某些意外错误,应用程序 panic 了。如何确保在应用程序退出前文件被正确关闭?

请您认真思考每个问题,并提供深思熟虑的答案。记住,这不是速度的问题,而是概念理解和详细性的问题!

English post: https://programmerscareer.com/1go-basic-15/
作者:Wesley Wei – Twitter Wesley Wei – Medium
注意:本文为作者原创,转载请注明出处。

Go基础:理解Go中的命令行、环境变量 Go基础:了解Go中的排序,文本模板,正则表达式,JSON, XML

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×