《Go 简易速速上手小册》第8章:网络编程(2024 最新版)(上)

简介: 《Go 简易速速上手小册》第8章:网络编程(2024 最新版)

4001a6010e053ebe5ba4462482fdd0d.png

8.1 HTTP 客户端与服务端编程 - Go 语言的网络灯塔与探航船

8.1.1 基础知识讲解

在Go语言的广阔海域中,HTTP客户端与服务端编程是连接世界的桥梁。Go通过其标准库提供了强大而灵活的工具,使得构建HTTP服务和发起HTTP请求变得简单直接。

服务端编程

Go的http包提供了构建HTTP服务的必要工具。通过定义处理函数并将其注册到路由上,Go应用可以响应各种HTTP请求:

http.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, Go Navigator!")
})
http.ListenAndServe(":8080", nil)

这段代码启动了一个HTTP服务,监听8080端口,并对/greeting路径的请求返回问候信息。

客户端编程

同样地,Go的http包也支持发起HTTP请求。这允许Go应用作为客户端,与其他HTTP服务进行交互:

resp, err := http.Get("https://api.example.com/data")
if err != nil {
    log.Fatalf("Cannot retrieve data: %v", err)
}
defer resp.Body.Close()
// 解析响应体...

这段代码向https://api.example.com/data发起GET请求,并处理返回的响应。

8.1.2 重点案例:简易博客服务

在这个扩展案例中,我们将构建一个简易的博客服务,该服务将支持文章的创建和列出所有文章的功能。此外,我们将提供一个简单的客户端示例,展示如何与这个服务进行交云。

服务端实现

我们的服务端将提供两个HTTP端点:一个用于接收新文章的提交(POST请求),另一个用于获取所有文章的列表(GET请求)。

// blogserver/main.go
package main
import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "sync"
)
type Article struct {
    ID      int    `json:"id"`
    Title   string `json:"title"`
    Content string `json:"content"`
}
var (
    articles []Article
    mu       sync.Mutex
    nextID   = 1
)
func postArticleHandler(w http.ResponseWriter, r *http.Request) {
    var article Article
    if err := json.NewDecoder(r.Body).Decode(&article); err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }
    mu.Lock()
    article.ID = nextID
    nextID++
    articles = append(articles, article)
    mu.Unlock()
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(article)
}
func listArticlesHandler(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    defer mu.Unlock()
    json.NewEncoder(w).Encode(articles)
}
func main() {
    http.HandleFunc("/articles", func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case "GET":
            listArticlesHandler(w, r)
        case "POST":
            postArticleHandler(w, r)
        default:
            w.WriteHeader(http.StatusMethodNotAllowed)
        }
    })
    fmt.Println("Blog server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

客户端实现

客户端代码将展示如何发起请求以创建新文章,以及如何获取文章列表。

// blogclient/main.go
package main
import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)
func createArticle(title, content string) {
    article := map[string]string{
        "title":   title,
        "content": content,
    }
    articleJSON, _ := json.Marshal(article)
    resp, err := http.Post("http://localhost:8080/articles", "application/json", bytes.NewBuffer(articleJSON))
    if err != nil {
        fmt.Println("Error creating article:", err)
        return
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println("Create Article Response:", string(body))
}
func getArticles() {
    resp, err := http.Get("http://localhost:8080/articles")
    if err != nil {
        fmt.Println("Error fetching articles:", err)
        return
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println("Articles List:", string(body))
}
func main() {
    createArticle("Go Concurrency", "Understanding Goroutines and Channels")
    createArticle("Go Web Programming", "Building Web Apps with Go")
    getArticles()
}

这个简易的客户端示例首先创建两篇文章,然后获取并打印所有文章的列表。

运行示例

  1. 启动服务端:在blogserver目录下运行go run main.go,启动博客服务。
  2. 运行客户端:在blogclient目录下运行go run main.go,通过客户端与服务端交互。

通过本案例,我们探索了如何使用Go的http包来构建一个简单的HTTP服务端和客户端。这个简易博客服务的实现展示了Go在网络编程领域的强大能力,提供了对HTTP请求的处理和响应的清晰示例。继续利用Go构建更多网络应用,探索更广阔的编程海洋吧!

8.1.3 拓展案例 1:增加文章评论功能

在我们的简易博客服务中,增加文章评论功能将使读者能够对文章进行互动。这要求我们在服务端添加新的逻辑来接收、存储和展示评论。

功能描述

  1. 接收评论:允许用户对特定文章添加评论。
  2. 存储评论:将评论与对应的文章关联存储。
  3. 展示评论:在请求文章详情时,一并返回其评论。

服务端实现

首先,我们需要在服务端扩展我们的数据模型和HTTP处理函数,以支持评论功能:

// blogserver/main.go
package main
import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "sync"
)
type Article struct {
    ID      int       `json:"id"`
    Title   string    `json:"title"`
    Content string    `json:"content"`
    Comments []string `json:"comments,omitempty"`
}
var (
    articles []Article
    mu       sync.Mutex
    nextID   = 1
)
// 新增添加评论的处理函数
func addCommentHandler(w http.ResponseWriter, r *http.Request) {
    articleID := r.URL.Query().Get("id")
    var comment string
    if err := json.NewDecoder(r.Body).Decode(&comment); err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }
    mu.Lock()
    defer mu.Unlock()
    for i, article := range articles {
        if article.ID == articleID {
            articles[i].Comments = append(articles[i].Comments, comment)
            json.NewEncoder(w).Encode(article)
            return
        }
    }
    http.Error(w, "Article not found", http.StatusNotFound)
}
func main() {
    http.HandleFunc("/articles", articlesHandler) // Assume articlesHandler handles both GET for listing and POST for creating articles.
    http.HandleFunc("/add_comment", addCommentHandler)
    fmt.Println("Blog server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

客户端实现

对于客户端,我们需要提供一个新的函数来发起添加评论的请求:

// blogclient/main.go
package main
import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "strconv"
)
func addComment(articleID int, comment string) {
    commentJSON, _ := json.Marshal(comment)
    resp, err := http.Post("http://localhost:8080/add_comment?id="+strconv.Itoa(articleID), "application/json", bytes.NewBuffer(commentJSON))
    if err != nil {
        fmt.Println("Error adding comment:", err)
        return
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println("Add Comment Response:", string(body))
}
func main() {
    // 示例:向ID为1的文章添加评论
    addComment(1, "Great article!")
}

通过这个扩展案例,我们为简易博客服务增加了文章评论功能,使得用户可以对文章进行评论,并在请求文章时查看到这些评论。这个案例展示了如何在现有的Go HTTP服务中添加新的功能,以及如何处理更复杂的数据关联和更新逻辑。继续探索Go在网络编程方面的能力,构建更加丰富和互动的应用吧!

8.1.4 拓展案例 2:实现文章的搜索功能

在我们的简易博客服务中,实现文章的搜索功能将极大地提升用户体验,允许用户根据关键词快速找到他们感兴趣的文章。这要求我们在服务端添加一个新的端点来处理搜索请求,并在文章数据中进行匹配。

功能描述

  1. 搜索文章:根据用户输入的关键词返回匹配的文章列表。
  2. 关键词匹配:在文章的标题和内容中查找包含关键词的文章。
  3. 返回结果:返回匹配的文章列表给用户。

服务端实现

首先,我们需要在服务端实现搜索功能的逻辑:

// blogserver/main.go
package main
import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strings"
    "sync"
)
type Article struct {
    ID      int    `json:"id"`
    Title   string `json:"title"`
    Content string `json:"content"`
}
var (
    articles []Article
    mu       sync.Mutex
)
// 实现搜索文章的处理函数
func searchArticlesHandler(w http.ResponseWriter, r *http.Request) {
    query := r.URL.Query().Get("q")
    if query == "" {
        http.Error(w, "Query parameter 'q' is required", http.StatusBadRequest)
        return
    }
    mu.Lock()
    defer mu.Unlock()
    var matchedArticles []Article
    for _, article := range articles {
        if strings.Contains(article.Title, query) || strings.Contains(article.Content, query) {
            matchedArticles = append(matchedArticles, article)
        }
    }
    json.NewEncoder(w).Encode(matchedArticles)
}
func main() {
    http.HandleFunc("/search", searchArticlesHandler)
    fmt.Println("Blog server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

在上述代码中,我们为博客服务添加了/search端点,用户可以通过这个端点传递一个查询参数q来搜索文章。搜索逻辑简单地检查文章标题和内容中是否包含了查询关键词,然后返回匹配的文章列表。

客户端示例

客户端可以通过发起HTTP GET请求到/search端点并传递查询参数来实现文章搜索:

// 示例:在客户端代码中搜索文章
package main
import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
)
func searchArticles(query string) {
    baseURL := "http://localhost:8080/search"
    queryParams := url.Values{}
    queryParams.Set("q", query)
    searchURL := baseURL + "?" + queryParams.Encode()
    resp, err := http.Get(searchURL)
    if err != nil {
        fmt.Println("Error searching articles:", err)
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response body:", err)
        return
    }
    fmt.Printf("Search Results for '%s':\n%s\n", query, string(body))
}
func main() {
    searchArticles("Go") // 搜索包含"Go"的文章
}

通过这个扩展案例,我们为简易博客服务增加了搜索功能,允许用户根据关键词搜索文章。这个案例演示了如何在Go HTTP服务中处理查询参数,并根据这些参数执行简单的搜索逻辑。随着你的Go应用变得更加复杂,考虑引入更高级的搜索技术,如全文搜索引擎,以提供更快速、更精确的搜索能力。继续探索Go在网络编程方面的强大功能,开发出更加丰富和高效的网络应用。

8.2 使用 Goroutines 处理并发请求 - Go 语言的并发船队

在Go语言的并发海洋中,goroutines是轻巧且强大的帆船,能够高效地在处理器的海浪上航行。通过goroutines,Go应用可以同时处理数百、数千乃至更多的任务,而不会因为阻塞操作而停滞不前。

8.2.1 基础知识讲解

Goroutines 的启动

goroutine是Go中实现并发的基础。启动一个goroutine就像是给帆船一阵风,让它启航:

go func() {
    // 并发执行的代码
}()

只需在函数调用前加上go关键字,该函数就会在新的goroutine中异步执行,主程序会继续向下执行而不会等待。

并发的 HTTP 请求处理

在HTTP服务中使用goroutines可以让我们并发地处理多个请求,大大提高了服务的吞吐量。每当收到一个新的请求,我们就可以为其分配一个goroutine,这样即使某些请求的处理时间较长,也不会阻塞其他请求的处理。

8.2.2 重点案例:并发下载服务

在这个案例中,我们将构建一个并发下载服务,该服务通过goroutines允许用户同时下载多个文件。这种并发处理模式显著提高了应用的效率,特别是在处理多个网络IO密集型任务时。

服务端实现

我们的服务端提供一个HTTP接口,接受一个包含多个文件URLs的请求,并为每个下载任务启动一个goroutine

// downloadservice/main.go
package main
import (
    "fmt"
    "io"
    "net/http"
    "os"
    "sync"
)
// downloadFile函数接收文件URL和目标文件名,完成文件下载
func downloadFile(url, filename string, wg *sync.WaitGroup) {
    defer wg.Done()
    resp, err := http.Get(url)
    if err != nil {
        fmt.Printf("Failed to download %s: %v\n", url, err)
        return
    }
    defer resp.Body.Close()
    out, err := os.Create(filename)
    if err != nil {
        fmt.Printf("Failed to create file %s: %v\n", filename, err)
        return
    }
    defer out.Close()
    _, err = io.Copy(out, resp.Body)
    if err != nil {
        fmt.Printf("Failed to write to file %s: %v\n", filename, err)
        return
    }
    fmt.Printf("Successfully downloaded %s to %s\n", url, filename)
}
// downloadHandler处理下载请求,解析请求中的URLs并启动goroutines进行下载
func downloadHandler(w http.ResponseWriter, r *http.Request) {
    urls := r.URL.Query()["url"]
    if len(urls) == 0 {
        http.Error(w, "No URLs provided", http.StatusBadRequest)
        return
    }
    var wg sync.WaitGroup
    for i, url := range urls {
        wg.Add(1)
        go downloadFile(url, fmt.Sprintf("download%d", i+1), &wg)
    }
    wg.Wait()
    fmt.Fprintln(w, "All download tasks completed.")
}
func main() {
    http.HandleFunc("/download", downloadHandler)
    fmt.Println("Download service started on :8080")
    http.ListenAndServe(":8080", nil)
}

在这个实现中,我们定义了downloadFile函数来处理单个文件的下载逻辑。downloadHandler函数负责解析HTTP请求中包含的多个下载URL,为每个URL启动一个goroutine执行downloadFile函数,并使用sync.WaitGroup等待所有下载任务完成。

测试服务

为了测试这个并发下载服务,你可以使用以下方式发起一个包含多个下载URLs的请求:

  1. 使用curl工具:
curl "http://localhost:8080/download?url=http://example.com/file1.jpg&url=http://example.com/file2.jpg"
  1. 或者,使用Postman等API测试工具发起GET请求,并在请求的URL参数中添加多个url参数。

通过这个案例,我们演示了如何在Go中利用goroutinessync.WaitGroup构建一个支持并发处理的下载服务。这种模式不仅适用于文件下载,还可以应用于其他需要并发执行多个任务的场景,如批量数据处理、并发API请求等。随着你继续探索Go语言的并发特性,你将能够构建出更加强大和高效的应用。

8.2.3 拓展案例 1:并发图片处理服务

在这个案例中,我们将构建一个并发图片处理服务,该服务允许用户上传图片,并且并行执行多种图片处理任务,如调整大小、应用滤镜等。利用goroutines实现并发处理,可以显著提高服务的处理效率。

功能描述

  1. 图片上传:允许用户上传图片到服务。
  2. 并发图片处理:对上传的图片并发执行多种处理任务。
  3. 返回处理结果:将处理后的图片保存并提供下载链接或直接返回给用户。

服务端实现

为了简化示例,我们假设图片已经上传到服务器上的某个目录,服务端的任务是读取这些图片并并发地进行处理。

首先,我们需要一个库来帮助我们处理图片,这里我们使用github.com/disintegration/imaging库:

go get -u github.com/disintegration/imaging

接着,实现图片处理服务的核心逻辑:

// imageprocessingservice/main.go
package main
import (
    "fmt"
    "github.com/disintegration/imaging"
    "net/http"
    "os"
    "path/filepath"
    "sync"
)
func processImage(filePath string, wg *sync.WaitGroup) {
    defer wg.Done()
    srcImage, err := imaging.Open(filePath)
    if err != nil {
        fmt.Printf("Failed to open image %s: %v\n", filePath, err)
        return
    }
    // 示例:调整图片大小为200x200,并应用高斯模糊滤镜
    dstImage := imaging.Resize(srcImage, 200, 200, imaging.Lanczos)
    dstImage = imaging.Blur(dstImage, 2.0)
    outputPath := filepath.Join("processed", filepath.Base(filePath))
    err = imaging.Save(dstImage, outputPath)
    if err != nil {
        fmt.Printf("Failed to save processed image %s: %v\n", outputPath, err)
        return
    }
    fmt.Printf("Processed and saved image to %s\n", outputPath)
}
func imageHandler(w http.ResponseWriter, r *http.Request) {
    var wg sync.WaitGroup
    // 假设图片存储在"./uploads"目录
    files, err := filepath.Glob("./uploads/*")
    if err != nil {
        http.Error(w, "Failed to list images", http.StatusInternalServerError)
        return
    }
    for _, file := range files {
        wg.Add(1)
        go processImage(file, &wg)
    }
    wg.Wait()
    fmt.Fprintln(w, "All images processed.")
}
func main() {
    http.HandleFunc("/process-images", imageHandler)
    fmt.Println("Image processing service started on :8080")
    http.ListenAndServe(":8080", nil)
}

在上述代码中,processImage函数负责打开一个图片文件,执行一系列处理任务(在这个示例中是调整大小和应用模糊滤镜),然后保存处理后的图片到processed目录。imageHandler函数并发地对uploads目录中的所有图片调用processImage函数。

测试服务

启动服务后,你可以通过向/process-images端点发送HTTP请求来触发图片处理过程。这可以通过浏览器访问,或使用工具如curl进行:

curl http://localhost:8080/process-images

这个并发图片处理服务展示了如何利用goroutines提高处理效率,通过并行处理来快速完成大量的图片处理任务。你可以根据具体需求添加更多的图片处理功能,如调整亮度、对比度,或者应用更复杂的图像分析算法。继续探索Go语言的并发特性,为你的应用带来更强大的处理能力。

8.2.4 拓展案例 2:实时股票行情服务

在这个案例中,我们将构建一个实时股票行情服务,该服务使用goroutines并发查询多个股票代码的实时价格,并将查询结果合并后返回给客户端。这种并发处理方式能够显著提高数据获取的速度,特别适合需要实时响应的金融应用。

功能描述

  1. 并发查询股票价格:对于客户端提供的股票代码列表,服务端并发查询每个股票的实时价格。
  2. 合并查询结果:将所有查询结果合并后,一次性返回给客户端。
  3. 错误处理:对于查询失败的股票代码,返回错误信息。
服务端实现

为了模拟股票价格查询,我们假设有一个简单的函数fetchStockPrice模拟从外部API获取股票价格。实际应用中,你需要替换为真实的股票价格API调用。

// stockservice/main.go
package main
import (
    "encoding/json"
    "fmt"
    "math/rand"
    "net/http"
    "sync"
    "time"
)
type StockPrice struct {
    Symbol string  `json:"symbol"`
    Price  float64 `json:"price"`
    Error  string  `json:"error,omitempty"`
}
func fetchStockPrice(symbol string) StockPrice {
    // 模拟网络延迟
    time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
    // 模拟偶尔的查询失败
    if rand.Intn(10) > 7 {
        return StockPrice{Symbol: symbol, Error: "Failed to fetch price"}
    }
    // 模拟股票价格
    return StockPrice{Symbol: symbol, Price: rand.Float64() * 100}
}
func stockHandler(w http.ResponseWriter, r *http.Request) {
    symbols := r.URL.Query()["symbol"]
    var wg sync.WaitGroup
    results := make([]StockPrice, len(symbols))
    for i, symbol := range symbols {
        wg.Add(1)
        go func(i int, symbol string) {
            defer wg.Done()
            results[i] = fetchStockPrice(symbol)
        }(i, symbol)
    }
    wg.Wait()
    json.NewEncoder(w).Encode(results)
}
func main() {
    rand.Seed(time.Now().UnixNano())
    http.HandleFunc("/stocks", stockHandler)
    fmt.Println("Stock service started on :8080")
    http.ListenAndServe(":8080", nil)
}

在这段代码中,stockHandler函数接收一个包含股票代码的查询参数,为每个股票代码启动一个goroutine执行fetchStockPrice函数,并发地获取股票价格。使用sync.WaitGroup等待所有的查询任务完成,然后将结果合并后以JSON格式返回给客户端。

测试服务

你可以通过向/stocks端点发送HTTP GET请求并附带股票代码作为查询参数来测试服务:

curl "http://localhost:8080/stocks?symbol=AAPL&symbol=GOOGL&symbol=MSFT"

这个简单的实时股票行情服务展示了如何利用Go的并发特性来提高数据处理的速度。通过goroutines并发执行任务和sync.WaitGroup来同步任务结果,我们可以构建出响应迅速、性能卓越的实时服务。在实际应用中,你可以将此模式应用于任何需要并发数据获取和处理的场景,充分发挥Go在并发编程方面的优势。


《Go 简易速速上手小册》第8章:网络编程(2024 最新版)(下)+https://developer.aliyun.com/article/1487005

目录
相关文章
|
3月前
|
数据采集 Go 定位技术
使用go并发网络爬虫
使用go并发网络爬虫
|
3月前
|
安全 Java Go
为什么选择Go语言编写网络应用程序
为什么选择Go语言编写网络应用程序
|
4月前
|
网络协议 算法 Go
在go内置网络库中的路由和多路复用
【7月更文挑战第6天】本文介绍Go的`net/http`库提供基础的HTTP服务,`ListenAndServe`管理TCP连接,处理请求。处理程序默认使用`DefaultServeMux`。也可以选择多路复用模式ServeMux。它们的示例代码展示了自定义`ServeHTTP`结构体处理不同路由 。
67 2
|
6月前
|
监控 JavaScript 前端开发
《Go 简易速速上手小册》第8章:网络编程(2024 最新版)(下)
《Go 简易速速上手小册》第8章:网络编程(2024 最新版)
57 1
|
6月前
|
Kubernetes Cloud Native Go
《Go 简易速速上手小册》第10章:微服务与云原生应用(2024 最新版)(下)
《Go 简易速速上手小册》第10章:微服务与云原生应用(2024 最新版)
158 0
|
6月前
|
Cloud Native 算法 Go
《Go 简易速速上手小册》第10章:微服务与云原生应用(2024 最新版)(上)
《Go 简易速速上手小册》第10章:微服务与云原生应用(2024 最新版)
159 0
|
6月前
|
存储 SQL Go
《Go 简易速速上手小册》第9章:数据库交互(2024 最新版)(下)
《Go 简易速速上手小册》第9章:数据库交互(2024 最新版)
57 0
|
6月前
|
SQL 关系型数据库 Go
《Go 简易速速上手小册》第9章:数据库交互(2024 最新版)(上)
《Go 简易速速上手小册》第9章:数据库交互(2024 最新版)
38 0
|
Go vr&ar 编解码
golang ffmpeg 做网络直播
最近在公司做在线视频转码的工作,研究了下ffmpeg 最后直接研究了下网络直播,我是在我自己的mac 上面测试的,效果,还可以,先看看效果图吧 ffmpeg 我是通过brew安装 的,这步就略了 VLC这个播放器怎么安装的也略了 我先是在github上面找了一个开源的直播流工具 https://github.
3983 0
|
1天前
|
编译器 Go
go语言编译选项
【10月更文挑战第17天】
9 5